aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0490-Optimize-NibbleArray-to-use-pooled-buffers.patch
diff options
context:
space:
mode:
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.patch375
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
+ }
+ }
+ }