diff options
Diffstat (limited to 'Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch')
-rw-r--r-- | Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch new file mode 100644 index 0000000000..b6213c209b --- /dev/null +++ b/Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -0,0 +1,375 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar <[email protected]> +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 5b196201c0e35895a04e2a542ef7c753d0c469e1..4b7c4643f04448aaccc66f26a9dea2323bea420d 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -386,11 +386,11 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytes()); ++ nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytes()); ++ nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper + } + + nbttaglist.add(nbttagcompound2); +diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java +index 6c7c4e75670a7e08ba10c0231a2510bf985dab6b..b8b06c790adfa0246b1a6fb5eab1f63bf5ef8b0b 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorage.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorage.java +@@ -149,7 +149,7 @@ public abstract class LightEngineStorage<M extends LightEngineStorageArray<M>> e + protected NibbleArray j(long i) { + NibbleArray nibblearray = (NibbleArray) this.i.get(i); + +- return nibblearray != null ? nibblearray : new NibbleArray(); ++ return nibblearray != null ? nibblearray : new NibbleArray().markPoolSafe(); // Paper + } + + protected void a(LightEngineLayer<?, ?> lightenginelayer, long i) { +@@ -331,12 +331,12 @@ public abstract class LightEngineStorage<M extends LightEngineStorageArray<M>> e + + protected void a(long i, @Nullable NibbleArray nibblearray, boolean flag) { + if (nibblearray != null) { +- this.i.put(i, nibblearray); ++ NibbleArray remove = this.i.put(i, nibblearray); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + if (!flag) { + this.n.add(i); + } + } 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/LightEngineStorageArray.java b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +index 53199595da71a25710bd1ff8ee2868ee63edc0e1..37c44a89f28c44915fcae5a7e2c4797b1c123723 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +@@ -33,7 +33,9 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray< + + public void a(long i) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data +- this.data.queueUpdate(i, ((NibbleArray) this.data.getUpdating(i)).b()); // Paper - avoid copying light data ++ NibbleArray updating = this.data.getUpdating(i); // Paper - pool nibbles ++ this.data.queueUpdate(i, new NibbleArray().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone ++ if (updating.cleaner != null) MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it + this.c(); + } + +diff --git a/src/main/java/net/minecraft/server/LightEngineStorageSky.java b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +index 7cec18fcfc311d20beca244c0affe5d6c1849e46..a35e7b392c74fadf2760d1fc2021e98d33858cb5 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java +@@ -166,9 +166,9 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage + j = SectionPosition.a(j, EnumDirection.UP); + } + +- return new NibbleArray((new NibbleArrayFlat(nibblearray1, 0)).asBytes()); ++ return new NibbleArray().markPoolSafe(new NibbleArrayFlat(nibblearray1, 0).asBytes()); // Paper - mark pool use as safe (no auto cleaner) + } else { +- return new NibbleArray(); ++ return new NibbleArray().markPoolSafe(); // Paper - mark pool use as safe (no auto cleaner) + } + } + } +@@ -197,7 +197,7 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage + ((LightEngineStorageSky.a) this.f).a(i); + } + +- Arrays.fill(this.a(i, true).asBytes(), (byte) -1); ++ Arrays.fill(this.a(i, true).asBytesPoolSafe(), (byte) -1); // Paper + k = SectionPosition.c(SectionPosition.b(i)); + l = SectionPosition.c(SectionPosition.c(i)); + int i1 = SectionPosition.c(SectionPosition.d(i)); +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 996c8326387b5a7fe62db6a76e000144565cb85b..8cedfdd820cc02a76607b53e0b054fc74654f907 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -1,16 +1,75 @@ + package net.minecraft.server; + ++import com.destroystokyo.paper.util.pooled.PooledObjects; // Paper ++ ++import javax.annotation.Nonnull; + 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, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8)); ++ public static final PooledObjects<byte[]> BYTE_2048 = new PooledObjects<>(() -> new byte[2048], maxPoolSize); ++ 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 NibbleArray markPoolSafe(byte[] bytes) { ++ if (bytes != EMPTY_NIBBLE) this.a = bytes; ++ return markPoolSafe(); ++ } ++ public NibbleArray markPoolSafe() { ++ poolSafe = true; ++ return this; ++ } ++ 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 NibbleArray cloneAndSet(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE) { ++ this.a = BYTE_2048.acquire(); ++ System.arraycopy(bytes, 0, this.a, 0, 2048); ++ } ++ return this; ++ } ++ boolean poolSafe = false; ++ public java.lang.Runnable cleaner; ++ private void registerCleaner() { ++ if (!poolSafe) { ++ cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); ++ } else { ++ cleaner = MCUtil.once(() -> NibbleArray.releaseBytes(this.a)); ++ } ++ } ++ // 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 +103,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); +@@ -66,14 +126,36 @@ public class NibbleArray { + public byte[] asBytes() { + if (this.a == null) { + this.a = new byte[2048]; ++ } else { // Paper start ++ // Accessor may need this object past garbage collection so need to clone it and return pooled value ++ // If we know its safe for pre GC access, use asBytesPoolSafe(). If you just need read, use getIfSet() ++ Runnable cleaner = this.cleaner; ++ if (cleaner != null) { ++ this.a = this.a.clone(); ++ cleaner.run(); // release the previously pooled value ++ this.cleaner = null; ++ } ++ } ++ // Paper end ++ ++ return this.a; ++ } ++ ++ @Nonnull ++ public byte[] asBytesPoolSafe() { ++ if (this.a == null) { ++ this.a = BYTE_2048.acquire(); // Paper ++ registerCleaner(); // Paper + } + ++ //noinspection ConstantConditions + return this.a; + } ++ // Paper end + + 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..5e3efa1fa6c089df35971ce5c83da384f7dbd402 100644 +--- a/src/main/java/net/minecraft/server/NibbleArrayFlat.java ++++ b/src/main/java/net/minecraft/server/NibbleArrayFlat.java +@@ -8,7 +8,7 @@ public class NibbleArrayFlat extends NibbleArray { + + public NibbleArrayFlat(NibbleArray nibblearray, int i) { + super(128); +- System.arraycopy(nibblearray.asBytes(), i * 128, this.a, 0, 128); ++ System.arraycopy(nibblearray.getIfSet(), i * 128, this.a, 0, 128); // Paper + } + + @Override +@@ -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 6b70df646c6a690ab9437ead96c5ff097e4e12d2..a22f0cccecc85b4e4fe4603bcfa213f15c23db69 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +@@ -1,6 +1,8 @@ + package net.minecraft.server; + + import com.google.common.collect.Lists; ++import io.netty.channel.ChannelFuture; // Paper ++ + import java.io.IOException; + import java.util.Iterator; + import java.util.List; +@@ -17,14 +19,43 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + private List<byte[]> h; + private boolean i; + ++ // 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, ChannelFuture future) { ++ if (remainingSends.decrementAndGet() <= 0) { ++ // incase of any race conditions, schedule this delayed ++ MCUtil.scheduleTask(5, () -> { ++ if (remainingSends.get() == 0) { ++ cleaner1.run(); ++ cleaner2.run(); ++ } ++ }, "Light Packet Release"); ++ } ++ } ++ ++ @Override ++ public boolean hasFinishListener() { ++ return true; ++ } ++ ++ // Paper end + public PacketPlayOutLightUpdate() {} + + public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine, boolean flag) { + this.a = chunkcoordintpair.x; + this.b = chunkcoordintpair.z; + this.i = flag; +- 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)); +@@ -35,7 +66,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(nibblearray.getCloneIfSet()); // Paper + } + } + +@@ -44,7 +75,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.asBytes().clone()); ++ this.h.add(nibblearray1.getCloneIfSet()); // Paper + } + } + } +@@ -57,8 +88,8 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + this.i = flag; + 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; +@@ -66,7 +97,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + 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) { +@@ -78,7 +109,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> { + 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) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 01bf7320b5cbc38e278ca907aa324ee3e945805e..06e42b7db5385f9a357183552259e7b4491d9fd0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -273,14 +273,14 @@ public class CraftChunk implements Chunk { + sectionSkyLights[i] = emptyLight; + } else { + sectionSkyLights[i] = new byte[2048]; +- System.arraycopy(skyLightArray.asBytes(), 0, sectionSkyLights[i], 0, 2048); ++ System.arraycopy(skyLightArray.getIfSet(), 0, sectionSkyLights[i], 0, 2048); // Paper + } + NibbleArray emitLightArray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(x, i, z)); + if (emitLightArray == null) { + sectionEmitLights[i] = emptyLight; + } else { + sectionEmitLights[i] = new byte[2048]; +- System.arraycopy(emitLightArray.asBytes(), 0, sectionEmitLights[i], 0, 2048); ++ System.arraycopy(emitLightArray.getIfSet(), 0, sectionEmitLights[i], 0, 2048); // Paper + } + } + } |