aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch
diff options
context:
space:
mode:
authorKyle Wood <[email protected]>2021-04-24 17:01:33 -0500
committerKyle Wood <[email protected]>2021-04-25 18:37:43 -0500
commit3093b81fee3064603c368ab934eddf66ce304433 (patch)
treecb99f05b5f31de92c41af4cc40b4bef5f3cbf573 /Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch
parent1af696a05d21cbdd7b5a7170f95598c013257588 (diff)
downloadPaper-3093b81fee3064603c368ab934eddf66ce304433.tar.gz
Paper-3093b81fee3064603c368ab934eddf66ce304433.zip
Move patches
Diffstat (limited to 'Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch')
-rw-r--r--Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch394
1 files changed, 394 insertions, 0 deletions
diff --git a/Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch
new file mode 100644
index 0000000000..0b519eafdf
--- /dev/null
+++ b/Spigot-Server-Patches-Unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch
@@ -0,0 +1,394 @@
+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/network/protocol/game/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java
+index 247d969e7d1aa59d9650fce1032aaa09db3903e5..9050ff7180f63c1f5756570446c4d0a8cc767779 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java
++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java
+@@ -1,12 +1,16 @@
+ package net.minecraft.network.protocol.game;
+
+ import com.google.common.collect.Lists;
++import io.netty.channel.ChannelFuture; // Paper
++
+ import java.io.IOException;
+ import java.util.Iterator;
+ import java.util.List;
+ import net.minecraft.core.SectionPosition;
+ import net.minecraft.network.PacketDataSerializer;
+ import net.minecraft.network.protocol.Packet;
++import net.minecraft.server.MCUtil;
++import net.minecraft.server.level.EntityPlayer;
+ import net.minecraft.world.level.ChunkCoordIntPair;
+ import net.minecraft.world.level.EnumSkyBlock;
+ import net.minecraft.world.level.chunk.NibbleArray;
+@@ -24,14 +28,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));
+@@ -42,7 +75,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
+ }
+ }
+
+@@ -51,7 +84,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
+ }
+ }
+ }
+@@ -64,8 +97,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;
+@@ -73,7 +106,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) {
+@@ -85,7 +118,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/net/minecraft/world/level/chunk/NibbleArray.java b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java
+index 86b4db483787c5fd10461f7d7e90a772ee049599..b82420e9a5d42a4383d24921614fe613c640edb9 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java
++++ b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java
+@@ -1,18 +1,78 @@
+ // mc-dev import
+ package net.minecraft.world.level.chunk;
+
++import com.destroystokyo.paper.util.pooled.PooledObjects; // Paper
++
++import javax.annotation.Nonnull;
+ import javax.annotation.Nullable;
+ import net.minecraft.SystemUtils;
++import net.minecraft.server.MCUtil;
+
+ 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((Throwable) (new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length)));
+ }
+@@ -46,7 +106,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);
+@@ -68,14 +129,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/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java
+index e16e046d165330326ed220c9c440a637007f3137..91bcbf7156dd90b00e2d53bb6bff4abc44ecb721 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java
+@@ -435,11 +435,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/world/level/lighting/LightEngineStorage.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java
+index 5b1ff4ff87591dd4ff0b79e4ac6ff0494fc3d0f8..9ba9efb181b9607f25b7c921e69e4c59b182d429 100644
+--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java
++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java
+@@ -156,7 +156,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) {
+@@ -338,12 +338,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/world/level/lighting/LightEngineStorageArray.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java
+index ed7864c552054fc47c6010a094230ce4aebf1c54..da78d4c4b5f8af4648ac82d63c21f6a2a5b73ecb 100644
+--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java
++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java
+@@ -2,6 +2,7 @@ package net.minecraft.world.level.lighting;
+
+ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+ import javax.annotation.Nullable;
++import net.minecraft.server.MCUtil;
+ import net.minecraft.world.level.chunk.NibbleArray;
+
+ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray<M>> {
+@@ -34,7 +35,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/world/level/lighting/LightEngineStorageSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java
+index 64d37f4c6a8167f47c80953a388ea6635490563a..488403a6765598317faedc2d600ae82238e99e39 100644
+--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java
++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java
+@@ -172,9 +172,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)
+ }
+ }
+ }
+@@ -203,7 +203,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/world/level/lighting/NibbleArrayFlat.java b/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java
+index dc059d5f332fdf561c7e410ce9959154de25006a..883a0d94cc0aa9d7cd8ab1ce320af86a9eacd1b8 100644
+--- a/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java
++++ b/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java
+@@ -10,7 +10,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
+@@ -20,7 +20,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/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+index 8ade81a693286cdf65f8c0eeca2121a217c90350..98ab124b4dca9387b4793cef68a33c67aed64e21 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+@@ -299,14 +299,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
+ }
+ }
+ }