Optimize NibbleArray to use pooled buffers

Massively reduces memory allocation of 2048 byte buffers by using
an object pool for these.
This commit is contained in:
Aikar 2020-05-07 01:32:02 -04:00
parent bb418b5532
commit 9204e8c641
3 changed files with 255 additions and 20 deletions

View File

@ -2090,18 +2090,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+public final class PooledObjects<E> { +public final class PooledObjects<E> {
+ +
+ public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(new PooledObjectHandler<MutableInt>() { + public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 200, -1);
+ @Override
+ public MutableInt createNew() {
+ return new MutableInt();
+ }
+
+ @Override
+ public void onAcquire(final MutableInt value) {}
+
+ @Override
+ public void onRelease(final MutableInt value) {}
+ }, 200, -1);
+ +
+ private final PooledObjectHandler<E> handler; + private final PooledObjectHandler<E> handler;
+ private final int maxPoolSize; + private final int maxPoolSize;
@ -2171,16 +2160,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
+ /** This object is restricted from interacting with any pool */ + /** This object is restricted from interacting with any pool */
+ static interface PooledObjectHandler<E> { + public static interface PooledObjectHandler<E> {
+ +
+ /** + /**
+ * Must return a non-null object + * Must return a non-null object
+ */ + */
+ E createNew(); + E createNew();
+ +
+ void onAcquire(final E value); + default void onAcquire(final E value) {}
+ +
+ void onRelease(final E value); + default void onRelease(final E value) {}
+ } + }
+ +
+ protected static class IsolatedPool<E> { + protected static class IsolatedPool<E> {

View File

@ -4,7 +4,8 @@ Date: Wed, 6 May 2020 04:53:35 -0400
Subject: [PATCH] Optimize Network Manager and add advanced packet support Subject: [PATCH] Optimize Network Manager and add advanced packet support
Adds ability for 1 packet to bundle other packets to follow it Adds ability for 1 packet to bundle other packets to follow it
adds ability for a packet to delay sending more packets until a state is ready. Adds ability for a packet to delay sending more packets until a state is ready.
Adds ability to clean up a packet when it is finished (not sent, or finished encoding), such as freeing buffers
Removes synchronization from sending packets Removes synchronization from sending packets
Removes processing packet queue off of main thread Removes processing packet queue off of main thread
@ -93,7 +94,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener)); - this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener));
+ // Paper start - handle oversized packets better + // Paper start - handle oversized packets better
+ boolean connected = this.isConnected(); + boolean connected = this.isConnected();
+ if (!connected && !preparing) return; // Do nothing + if (!connected && !preparing) {
+ packet.onPacketDone();
+ return; // Do nothing
+ }
+ if (connected && (InnerUtil.canSendImmediate(this, packet) || ( + if (connected && (InnerUtil.canSendImmediate(this, packet) || (
+ MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && + MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() &&
+ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
@ -109,13 +113,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } else { + } else {
+ java.util.List<NetworkManager.QueuedPacket> packets = new java.util.ArrayList<>(1 + extraPackets.size()); + java.util.List<NetworkManager.QueuedPacket> packets = new java.util.ArrayList<>(1 + extraPackets.size());
+ packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets + packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets
+
+ for (int i = 0, len = extraPackets.size(); i < len;) { + for (int i = 0, len = extraPackets.size(); i < len;) {
+ Packet extra = extraPackets.get(i); + Packet extra = extraPackets.get(i);
+ boolean end = ++i == len; + boolean end = ++i == len;
+ packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end + packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end
+ } + }
+
+ this.packetQueue.addAll(packets); // atomic + this.packetQueue.addAll(packets); // atomic
+ } + }
+ this.sendPacketQueue(); + this.sendPacketQueue();
@ -181,13 +185,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public void a() { public void a() {
this.o(); this.o();
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
return this.socketAddress;
}
+ public void clearPacketQueue() { QueuedPacket packet; while ((packet = packetQueue.poll()) != null) packet.getPacket().onPacketDone(); } // Paper
public void close(IChatBaseComponent ichatbasecomponent) { public void close(IChatBaseComponent ichatbasecomponent) {
// Spigot Start // Spigot Start
this.preparing = false; this.preparing = false;
+ this.packetQueue.clear(); // Paper - just incase its closed before we ever get to the main thread to do this + clearPacketQueue(); // Paper
// Spigot End // Spigot End
if (this.channel.isOpen()) { if (this.channel.isOpen()) {
this.channel.close(); // We can't wait as this may be called from an event loop. this.channel.close(); // We can't wait as this may be called from an event loop.
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
} else if (this.i() != null) {
this.i().a(new ChatMessage("multiplayer.disconnect.generic", new Object[0]));
}
- this.packetQueue.clear(); // Free up packet queue.
+ clearPacketQueue(); // Paper
// Paper start - Add PlayerConnectionCloseEvent
final PacketListener packetListener = this.i();
if (packetListener instanceof PlayerConnection) {
diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/Packet.java --- a/src/main/java/net/minecraft/server/Packet.java
@ -196,11 +213,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
void a(T t0); void a(T t0);
// Paper start // Paper start
+ default void onPacketDone() {}
+ default boolean isReady() { return true; } + default boolean isReady() { return true; }
+ default java.util.List<Packet> getExtraPackets() { return null; } + default java.util.List<Packet> getExtraPackets() { return null; }
default boolean packetTooLarge(NetworkManager manager) { default boolean packetTooLarge(NetworkManager manager) {
return false; return false;
} }
diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PacketEncoder.java
+++ b/src/main/java/net/minecraft/server/PacketEncoder.java
@@ -0,0 +0,0 @@ public class PacketEncoder extends MessageToByteEncoder<Packet<?>> {
} else {
throw throwable;
}
- }
+ } finally { try { packet.onPacketDone(); } catch (Exception e) { e.printStackTrace(); } ; } // Paper
// Paper start
int packetLength = bytebuf.readableBytes();
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java --- a/src/main/java/net/minecraft/server/PlayerList.java

View File

@ -0,0 +1,215 @@
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.
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -0,0 +0,0 @@ public class ChunkRegionLoader {
}
if (nibblearray != null && !nibblearray.c()) {
- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytes());
+ nbttagcompound2.setByteArray("BlockLight", nibblearray.getIfSet()); // Paper
}
if (nibblearray1 != null && !nibblearray1.c()) {
- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytes());
+ nbttagcompound2.setByteArray("SkyLight", nibblearray1.getIfSet()); // Paper
}
nbttaglist.add(nbttagcompound2);
diff --git a/src/main/java/net/minecraft/server/LightEngineStorageSky.java b/src/main/java/net/minecraft/server/LightEngineStorageSky.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java
+++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java
@@ -0,0 +0,0 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
j = SectionPosition.a(j, EnumDirection.UP);
}
- return new NibbleArray((new NibbleArrayFlat(nibblearray1, 0)).asBytes());
+ return new NibbleArray((new NibbleArrayFlat(nibblearray1, 0)).asBytes(), true); // Paper - mark buffer as safe
} else {
return new NibbleArray();
}
diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/NibbleArray.java
+++ b/src/main/java/net/minecraft/server/NibbleArray.java
@@ -0,0 +0,0 @@
package net.minecraft.server;
+import com.destroystokyo.paper.util.pooled.PooledObjects;
+
import javax.annotation.Nullable;
public class NibbleArray {
- @Nullable
- protected byte[] a;
+ // Paper start
+ public static byte[] EMPTY_NIBBLE = new byte[2048];
+ public static final PooledObjects<byte[]> BYTE_2048 = new PooledObjects<>(() -> new byte[2048], 16384, 1);
+ public static void releaseBytes(byte[] bytes) {
+ if (bytes != EMPTY_NIBBLE) {
+ 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;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (this.a != null) {
+ releaseBytes(this.a);
+ this.a = null;
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+ // 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
+ // Paper end
if (abyte.length != 2048) {
throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length));
}
@@ -0,0 +0,0 @@ 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
}
int k = this.d(i);
@@ -0,0 +0,0 @@ public class NibbleArray {
public byte[] asBytes() {
if (this.a == null) {
- this.a = new byte[2048];
+ this.a = BYTE_2048.acquire(); // Paper
}
return this.a;
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/NibbleArrayFlat.java
+++ b/src/main/java/net/minecraft/server/NibbleArrayFlat.java
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
private List<byte[]> h;
public PacketPlayOutLightUpdate() {}
+ // Paper start
+ private final java.util.List<byte[]> usedBytes = new java.util.ArrayList<>();
+
+ @Override
+ public void onPacketDone() {
+ usedBytes.forEach(NibbleArray::releaseBytes);
+ usedBytes.clear();
+ }
+ // Paper end
public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine) {
this.a = chunkcoordintpair.x;
@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
this.g = Lists.newArrayList();
this.h = Lists.newArrayList();
+ byte[] lastBytes; // Paper
for (int i = 0; i < 18; ++i) {
NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i));
NibbleArray nibblearray1 = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + i));
@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
this.e |= 1 << i;
} else {
this.c |= 1 << i;
- this.g.add(nibblearray.asBytes().clone());
+ this.g.add(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
}
}
@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
this.f |= 1 << i;
} else {
this.d |= 1 << i;
- this.h.add(nibblearray1.asBytes().clone());
+ this.h.add(lastBytes = nibblearray1.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
}
}
}
@@ -0,0 +0,0 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
this.g = Lists.newArrayList();
this.h = Lists.newArrayList();
+ byte[] lastBytes; // Paper
for (int k = 0; k < 18; ++k) {
NibbleArray nibblearray;
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(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
} else {
this.c &= ~(1 << k);
if (nibblearray != null) {
@@ -0,0 +0,0 @@ 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(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
} else {
this.d &= ~(1 << k);
if (nibblearray != null) {