mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-03 06:40:19 +08:00
4e958e229f
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com> Co-authored-by: zml <zml@stellardrift.ca> Co-authored-by: Mariell Hoversholm <proximyst@proximyst.com>
376 lines
17 KiB
Diff
376 lines
17 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
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 20388f4305957a83a678119dcda0249105125ab1..f26740cd0f6a7758dc45cbacd4919d38bbdbb1c4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
@@ -392,11 +392,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 6d608dfa52fffd79878f3bdebcda9fab176e413e..ff00830b95a17f66d0c913087492dbf4b066df8a 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((Throwable) (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 531d5e41dfbfbfe8da0676207f6325a0bca97a9a..b345b7658b7de28787cb10255d7d881bc1493003 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
|
|
}
|
|
}
|
|
}
|