From 9204e8c641170cecaf1c749b7cc72dc0b972521e Mon Sep 17 00:00:00 2001 From: Aikar Date: Thu, 7 May 2020 01:32:02 -0400 Subject: [PATCH] Optimize NibbleArray to use pooled buffers Massively reduces memory allocation of 2048 byte buffers by using an object pool for these. --- Spigot-Server-Patches/MC-Utils.patch | 19 +- ...-Manager-and-add-advanced-packet-sup.patch | 41 +++- ...ze-NibbleArray-to-use-pooled-buffers.patch | 215 ++++++++++++++++++ 3 files changed, 255 insertions(+), 20 deletions(-) create mode 100644 Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch diff --git a/Spigot-Server-Patches/MC-Utils.patch b/Spigot-Server-Patches/MC-Utils.patch index f822aeec8f..b70f776a5e 100644 --- a/Spigot-Server-Patches/MC-Utils.patch +++ b/Spigot-Server-Patches/MC-Utils.patch @@ -2090,18 +2090,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +public final class PooledObjects { + -+ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(new PooledObjectHandler() { -+ @Override -+ public MutableInt createNew() { -+ return new MutableInt(); -+ } -+ -+ @Override -+ public void onAcquire(final MutableInt value) {} -+ -+ @Override -+ public void onRelease(final MutableInt value) {} -+ }, 200, -1); ++ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 200, -1); + + private final PooledObjectHandler handler; + private final int maxPoolSize; @@ -2171,16 +2160,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + /** This object is restricted from interacting with any pool */ -+ static interface PooledObjectHandler { ++ public static interface PooledObjectHandler { + + /** + * Must return a non-null object + */ + E createNew(); + -+ void onAcquire(final E value); ++ default void onAcquire(final E value) {} + -+ void onRelease(final E value); ++ default void onRelease(final E value) {} + } + + protected static class IsolatedPool { diff --git a/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch index 1c10af485b..636d2c5426 100644 --- a/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch +++ b/Spigot-Server-Patches/Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -4,7 +4,8 @@ Date: Wed, 6 May 2020 04:53:35 -0400 Subject: [PATCH] Optimize Network Manager and add advanced packet support Adds ability for 1 packet to bundle other packets to follow it -adds ability for a packet to delay sending more packets until a state is ready. +Adds ability for a packet to delay sending more packets until a state is ready. +Adds ability to clean up a packet when it is finished (not sent, or finished encoding), such as freeing buffers Removes synchronization from sending packets Removes processing packet queue off of main thread @@ -93,7 +94,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener)); + // Paper start - handle oversized packets better + boolean connected = this.isConnected(); -+ if (!connected && !preparing) return; // Do nothing ++ if (!connected && !preparing) { ++ packet.onPacketDone(); ++ return; // Do nothing ++ } + if (connected && (InnerUtil.canSendImmediate(this, packet) || ( + MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) @@ -109,13 +113,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } else { + java.util.List packets = new java.util.ArrayList<>(1 + extraPackets.size()); + packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets - ++ + for (int i = 0, len = extraPackets.size(); i < len;) { + Packet extra = extraPackets.get(i); + boolean end = ++i == len; + packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end + } -+ + + this.packetQueue.addAll(packets); // atomic + } + this.sendPacketQueue(); @@ -181,13 +185,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public void a() { this.o(); @@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + return this.socketAddress; + } + ++ public void clearPacketQueue() { QueuedPacket packet; while ((packet = packetQueue.poll()) != null) packet.getPacket().onPacketDone(); } // Paper public void close(IChatBaseComponent ichatbasecomponent) { // Spigot Start this.preparing = false; -+ this.packetQueue.clear(); // Paper - just incase its closed before we ever get to the main thread to do this ++ clearPacketQueue(); // Paper // Spigot End if (this.channel.isOpen()) { this.channel.close(); // We can't wait as this may be called from an event loop. +@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } else if (this.i() != null) { + this.i().a(new ChatMessage("multiplayer.disconnect.generic", new Object[0])); + } +- this.packetQueue.clear(); // Free up packet queue. ++ clearPacketQueue(); // Paper + // Paper start - Add PlayerConnectionCloseEvent + final PacketListener packetListener = this.i(); + if (packetListener instanceof PlayerConnection) { diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/Packet.java @@ -196,11 +213,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 void a(T t0); // Paper start ++ default void onPacketDone() {} + default boolean isReady() { return true; } + default java.util.List getExtraPackets() { return null; } default boolean packetTooLarge(NetworkManager manager) { return false; } +diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PacketEncoder.java ++++ b/src/main/java/net/minecraft/server/PacketEncoder.java +@@ -0,0 +0,0 @@ public class PacketEncoder extends MessageToByteEncoder> { + } else { + throw throwable; + } +- } ++ } finally { try { packet.onPacketDone(); } catch (Exception e) { e.printStackTrace(); } ; } // Paper + + // Paper start + int packetLength = bytebuf.readableBytes(); diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java diff --git a/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch new file mode 100644 index 0000000000..92d8ac8739 --- /dev/null +++ b/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 23:30:30 -0400 +Subject: [PATCH] Optimize NibbleArray to use pooled buffers + +Massively reduces memory allocation of 2048 byte buffers by using +an object pool for these. + +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -0,0 +0,0 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytes()); ++ nbttagcompound2.setByteArray("BlockLight", nibblearray.getIfSet()); // Paper + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytes()); ++ nbttagcompound2.setByteArray("SkyLight", nibblearray1.getIfSet()); // Paper + } + + nbttaglist.add(nbttagcompound2); +diff --git a/src/main/java/net/minecraft/server/LightEngineStorageSky.java b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +@@ -0,0 +0,0 @@ public class LightEngineStorageSky extends LightEngineStorage BYTE_2048 = new PooledObjects<>(() -> new byte[2048], 16384, 1); ++ public static void releaseBytes(byte[] bytes) { ++ if (bytes != EMPTY_NIBBLE) { ++ System.arraycopy(EMPTY_NIBBLE, 0, bytes, 0, 2048); ++ BYTE_2048.release(bytes); ++ } ++ } ++ ++ public byte[] getIfSet() { ++ return this.a != null ? this.a : EMPTY_NIBBLE; ++ } ++ public byte[] getCloneIfSet() { ++ if (a == null) { ++ return EMPTY_NIBBLE; ++ } ++ byte[] ret = BYTE_2048.acquire(); ++ System.arraycopy(getIfSet(), 0, ret, 0, 2048); ++ return ret; ++ } ++ ++ @Override ++ protected void finalize() throws Throwable { ++ try { ++ if (this.a != null) { ++ releaseBytes(this.a); ++ this.a = null; ++ } ++ } finally { ++ super.finalize(); ++ } ++ } ++ // Paper end ++ @Nullable protected byte[] a; ++ + + public NibbleArray() {} + + public NibbleArray(byte[] abyte) { ++ // Paper start ++ this(abyte, false); ++ } ++ public NibbleArray(byte[] abyte, boolean isSafe) { + this.a = abyte; ++ if (!isSafe) this.a = getCloneIfSet(); // Paper - clone for safety ++ // Paper end + if (abyte.length != 2048) { + throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length)); + } +@@ -0,0 +0,0 @@ public class NibbleArray { + + public void a(int i, int j) { // PAIL: private -> public + if (this.a == null) { +- this.a = new byte[2048]; ++ this.a = BYTE_2048.acquire(); // Paper + } + + int k = this.d(i); +@@ -0,0 +0,0 @@ public class NibbleArray { + + public byte[] asBytes() { + if (this.a == null) { +- this.a = new byte[2048]; ++ this.a = BYTE_2048.acquire(); // Paper + } + + return this.a; +@@ -0,0 +0,0 @@ public class NibbleArray { + + public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER + public NibbleArray b() { +- return this.a == null ? new NibbleArray() : new NibbleArray((byte[]) this.a.clone()); ++ return this.a == null ? new NibbleArray() : new NibbleArray(this.a); // Paper - clone in ctor + } + + public String toString() { +diff --git a/src/main/java/net/minecraft/server/NibbleArrayFlat.java b/src/main/java/net/minecraft/server/NibbleArrayFlat.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/NibbleArrayFlat.java ++++ b/src/main/java/net/minecraft/server/NibbleArrayFlat.java +@@ -0,0 +0,0 @@ public class NibbleArrayFlat extends NibbleArray { + + @Override + public byte[] asBytes() { +- byte[] abyte = new byte[2048]; ++ byte[] abyte = BYTE_2048.acquire(); // Paper + + for (int i = 0; i < 16; ++i) { + System.arraycopy(this.a, 0, abyte, i * 128, 128); +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + private List h; + + public PacketPlayOutLightUpdate() {} ++ // Paper start ++ private final java.util.List usedBytes = new java.util.ArrayList<>(); ++ ++ @Override ++ public void onPacketDone() { ++ usedBytes.forEach(NibbleArray::releaseBytes); ++ usedBytes.clear(); ++ } ++ // Paper end + + public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine) { + this.a = chunkcoordintpair.x; +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + this.g = Lists.newArrayList(); + this.h = Lists.newArrayList(); + ++ byte[] lastBytes; // Paper + for (int i = 0; i < 18; ++i) { + NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); + NibbleArray nibblearray1 = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + i)); +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper + } + } + +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.asBytes().clone()); ++ this.h.add(lastBytes = nibblearray1.getCloneIfSet()); usedBytes.add(lastBytes); // Paper + } + } + } +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + this.g = Lists.newArrayList(); + this.h = Lists.newArrayList(); + ++ byte[] lastBytes; // Paper + for (int k = 0; k < 18; ++k) { + NibbleArray nibblearray; + + if ((this.c & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper + } else { + this.c &= ~(1 << k); + if (nibblearray != null) { +@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.d & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.h.add(nibblearray.asBytes().clone()); ++ this.h.add(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper + } else { + this.d &= ~(1 << k); + if (nibblearray != null) {