diff --git a/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch new file mode 100644 index 0000000000..3841119461 --- /dev/null +++ b/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -0,0 +1,277 @@ +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. + +Uses lots of advanced new capabilities of the Paper codebase :) + +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index e625842e524f18e469f7695b27d52d4d04892266..82264bf651b9d1f6614fef5efc29419947407eca 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -387,11 +387,15 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytes()); ++ byte[] bytes = nibblearray.getCloneIfSet(); // Paper ++ nbttagcompound2.setByteArray("BlockLight", bytes); // Paper ++ MCUtil.registerCleaner(nbttagcompound2, bytes, NibbleArray::releaseBytes); // Paper - we can't really hook when this chunk is done without a ton of yuck efort, so just reclaim it once GC gets to it. + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytes()); ++ byte[] bytes = nibblearray1.getCloneIfSet(); // Paper ++ nbttagcompound2.setByteArray("SkyLight", bytes); // Paper ++ MCUtil.registerCleaner(nbttagcompound2, bytes, NibbleArray::releaseBytes); // Paper - we can't really hook when this chunk is done without a ton of yuck efort, so just reclaim it once GC gets to it. + } + + nbttaglist.add(nbttagcompound2); +diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java +index 88277d23c36696fdd5363e41a130c9a443fac2c0..1a048cf586eac76499522599a0cac91e31472d72 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorage.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorage.java +@@ -319,7 +319,7 @@ public abstract class LightEngineStorage> e + if (nibblearray != null) { + this.i.put(i, nibblearray); + } else { +- this.i.remove(i); ++ NibbleArray remove = this.i.remove(i); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + } + + } +diff --git a/src/main/java/net/minecraft/server/LightEngineStorageSky.java b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +index 06bc8371fe9de4d23fdd47e5a3919541bb399fd8..bf37e4ec1f3f4f73c27e1eecffa96423f683a10b 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +@@ -166,7 +166,7 @@ public class LightEngineStorageSky extends LightEngineStorage { + com.google.common.base.Preconditions.checkArgument( j < 1 << 24); // Spigot + + nbtreadlimiter.a(8L * (long) j); +- byte[] abyte = new byte[j]; ++ byte[] abyte = j == 2048 ? NibbleArray.BYTE_2048.acquire() : new byte[j]; // Paper - use nibble buffer if right size + + datainput.readFully(abyte); +- return new NBTTagByteArray(abyte); ++ // Paper start - use pooled ++ NBTTagByteArray nbt = new NBTTagByteArray(abyte); ++ if (abyte.length == 2048) { ++ // register cleaner ++ MCUtil.registerCleaner(nbt, abyte, NibbleArray::releaseBytes); ++ } ++ return nbt; ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 996c8326387b5a7fe62db6a76e000144565cb85b..af32197dc82e3ebf32ff2ced954f7b4fe3ec9e21 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -1,16 +1,50 @@ + package net.minecraft.server; + ++import com.destroystokyo.paper.util.pooled.PooledObjects; // Paper + import javax.annotation.Nullable; + + public class NibbleArray { + +- @Nullable +- protected byte[] a; ++ // Paper start ++ public static byte[] EMPTY_NIBBLE = new byte[2048]; ++ private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); ++ private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024) * (nibbleBucketSizeMultiplier * 8)); ++ public static final PooledObjects BYTE_2048 = new PooledObjects<>(() -> new byte[2048], maxPoolSize, 8); ++ public static void releaseBytes(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE && bytes.length == 2048) { ++ 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; ++ } ++ public java.lang.Runnable cleaner; ++ private void registerCleaner() { cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); } ++ // 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 ++ registerCleaner(); ++ // Paper end + if (abyte.length != 2048) { + throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length)); + } +@@ -44,7 +78,8 @@ 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 ++ registerCleaner();// Paper + } + + int k = this.d(i); +@@ -65,7 +100,8 @@ public class NibbleArray { + + public byte[] asBytes() { + if (this.a == null) { +- this.a = new byte[2048]; ++ this.a = BYTE_2048.acquire(); // Paper ++ registerCleaner();// Paper + } + + return this.a; +@@ -73,7 +109,7 @@ 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 67c960292db9d99ac85b5d0dda50ae48ef942c1b..f7641156beea365a91a935667abf8c9539896dc0 100644 +--- a/src/main/java/net/minecraft/server/NibbleArrayFlat.java ++++ b/src/main/java/net/minecraft/server/NibbleArrayFlat.java +@@ -18,7 +18,7 @@ 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 cd1ad45469aa163b9bc41774ae80adfa617fd97b..1946aae7424593100279834baa89c19f2522437f 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +@@ -16,13 +16,42 @@ public class PacketPlayOutLightUpdate implements Packet { + private List g; + private List h; + ++ // Paper start ++ java.lang.Runnable cleaner1; ++ java.lang.Runnable cleaner2; ++ java.util.concurrent.atomic.AtomicInteger remainingSends = new java.util.concurrent.atomic.AtomicInteger(0); ++ ++ @Override ++ public void onPacketDispatch(EntityPlayer player) { ++ remainingSends.incrementAndGet(); ++ } ++ ++ @Override ++ public void onPacketDispatchFinish(EntityPlayer player, io.netty.channel.ChannelFuture future) { ++ if (remainingSends.decrementAndGet() <= 0) { ++ // incase of any race conditions, schedule this delayed ++ MCUtil.scheduleTask(1, () -> { ++ if (remainingSends.get() == 0) { ++ cleaner1.run(); ++ cleaner2.run(); ++ } ++ }); ++ } ++ } ++ ++ @Override ++ public boolean hasFinishListener() { ++ return true; ++ } ++ ++ // Paper end + public PacketPlayOutLightUpdate() {} + + public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine) { + this.a = chunkcoordintpair.x; + this.b = chunkcoordintpair.z; +- this.g = Lists.newArrayList(); +- this.h = Lists.newArrayList(); ++ this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper ++ this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper + + for (int i = 0; i < 18; ++i) { + NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); +@@ -33,7 +62,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(nibblearray.getCloneIfSet()); // Paper + } + } + +@@ -42,7 +71,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.asBytes().clone()); ++ this.h.add(nibblearray1.getCloneIfSet()); // Paper + } + } + } +@@ -54,8 +83,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.b = chunkcoordintpair.z; + this.c = i; + this.d = j; +- this.g = Lists.newArrayList(); +- this.h = Lists.newArrayList(); ++ this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper ++ this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper + + for (int k = 0; k < 18; ++k) { + NibbleArray nibblearray; +@@ -63,7 +92,7 @@ public class PacketPlayOutLightUpdate implements Packet { + 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(nibblearray.getCloneIfSet()); // Paper + } else { + this.c &= ~(1 << k); + if (nibblearray != null) { +@@ -75,7 +104,7 @@ 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(nibblearray.getCloneIfSet()); // Paper + } else { + this.d &= ~(1 << k); + if (nibblearray != null) {