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 d0a528b1cb
commit 03c9bb05c1
No known key found for this signature in database
GPG Key ID: 401ADFC9891FAAFE
3 changed files with 263 additions and 28 deletions

View File

@ -2077,10 +2077,10 @@ index 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef
+} +}
diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..d4325f1f1152e34f82046dfe24cbe505f6e316ae index 0000000000000000000000000000000000000000..e272b512520486cf7d46fe4e1021ca148d4cf74f
--- /dev/null --- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java +++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
@@ -0,0 +1,185 @@ @@ -0,0 +1,174 @@
+package com.destroystokyo.paper.util.pooled; +package com.destroystokyo.paper.util.pooled;
+ +
+import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.mutable.MutableInt;
@ -2090,18 +2090,7 @@ index 0000000000000000000000000000000000000000..d4325f1f1152e34f82046dfe24cbe505
+ +
+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..d4325f1f1152e34f82046dfe24cbe505
+ } + }
+ +
+ /** 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
@ -23,7 +24,7 @@ This should solve some deadlock risks
Part of this commit was authored by: Spottedleaf Part of this commit was authored by: Spottedleaf
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19fd7d6ac9 100644 index b1dededc15cce686ead74a99bee64c89ac1de22c..d24d7df807287c51a8dd5ecf4f7c842b7cdf3976 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java --- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -64,6 +64,10 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -64,6 +64,10 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
@ -45,7 +46,7 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19
this.channel.attr(NetworkManager.c).set(enumprotocol); this.channel.attr(NetworkManager.c).set(enumprotocol);
this.channel.config().setAutoRead(true); this.channel.config().setAutoRead(true);
NetworkManager.LOGGER.debug("Enabled auto read"); NetworkManager.LOGGER.debug("Enabled auto read");
@@ -158,19 +163,72 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -158,19 +163,75 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
NetworkManager.LOGGER.debug("Set listener of {} to {}", this, packetlistener); NetworkManager.LOGGER.debug("Set listener of {} to {}", this, packetlistener);
this.packetListener = packetlistener; this.packetListener = packetlistener;
} }
@ -93,7 +94,10 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19
- 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 b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19
+ } 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();
@ -123,7 +127,7 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19
} }
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
@@ -214,21 +272,46 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -214,21 +275,46 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
} }
@ -180,27 +184,54 @@ index b1dededc15cce686ead74a99bee64c89ac1de22c..f81014973a4cf50c1320f82c22ed1c19
public void a() { public void a() {
this.o(); this.o();
@@ -260,6 +343,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> { @@ -257,9 +343,11 @@ 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.
@@ -335,7 +423,7 @@ 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 2d8e6a2f4a0c3c5d74a647d7164b0028781d3bf5..45142ed9d2440e21dd1ff1a32a12759fea4dbcb4 100644 index 2d8e6a2f4a0c3c5d74a647d7164b0028781d3bf5..df1b4877b1560f982a1fcaf98404c8fe73e29973 100644
--- a/src/main/java/net/minecraft/server/Packet.java --- a/src/main/java/net/minecraft/server/Packet.java
+++ b/src/main/java/net/minecraft/server/Packet.java +++ b/src/main/java/net/minecraft/server/Packet.java
@@ -11,6 +11,8 @@ public interface Packet<T extends PacketListener> { @@ -11,6 +11,9 @@ public interface Packet<T extends PacketListener> {
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 b0cfef52cbb5e23beae528668e4e98cedecf603c..f46d028016a425a29674e768ae9310c825c088f2 100644
--- a/src/main/java/net/minecraft/server/PacketEncoder.java
+++ b/src/main/java/net/minecraft/server/PacketEncoder.java
@@ -48,7 +48,7 @@ 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 e148940ab3721cff27cf791c159c11b9b94191e4..e917d37382dab70ed9e6b62decf1557c33b26065 100644 index e148940ab3721cff27cf791c159c11b9b94191e4..e917d37382dab70ed9e6b62decf1557c33b26065 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 fa893b14bcef9bab6891dea2c4375b09d74ac038..738f5b975697cd83f1ff35eb0d2d8619ee8d39e4 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -387,11 +387,11 @@ 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 06bc8371fe9de4d23fdd47e5a3919541bb399fd8..bf37e4ec1f3f4f73c27e1eecffa96423f683a10b 100644
--- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java
+++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java
@@ -166,7 +166,7 @@ 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 996c8326387b5a7fe62db6a76e000144565cb85b..6423ec30504b53ffe2d400b7363cccc60208e31f 100644
--- a/src/main/java/net/minecraft/server/NibbleArray.java
+++ b/src/main/java/net/minecraft/server/NibbleArray.java
@@ -1,16 +1,58 @@
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));
}
@@ -44,7 +86,7 @@ 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);
@@ -65,7 +107,7 @@ public class NibbleArray {
public byte[] asBytes() {
if (this.a == null) {
- this.a = new byte[2048];
+ this.a = BYTE_2048.acquire(); // Paper
}
return this.a;
@@ -73,7 +115,7 @@ 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 67c960292db9d99ac85b5d0dda50ae48ef942c1b..f7641156beea365a91a935667abf8c9539896dc0 100644
--- a/src/main/java/net/minecraft/server/NibbleArrayFlat.java
+++ b/src/main/java/net/minecraft/server/NibbleArrayFlat.java
@@ -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 cd1ad45469aa163b9bc41774ae80adfa617fd97b..d6560f4693869a6638963867f7ebc63bf80d534e 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
@@ -17,6 +17,15 @@ 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;
@@ -24,6 +33,7 @@ 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));
@@ -33,7 +43,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(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
}
}
@@ -42,7 +52,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(lastBytes = nibblearray1.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
}
}
}
@@ -57,13 +67,14 @@ 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) {
@@ -75,7 +86,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(lastBytes = nibblearray.getCloneIfSet()); usedBytes.add(lastBytes); // Paper
} else {
this.d &= ~(1 << k);
if (nibblearray != null) {