diff --git a/Spigot-Server-Patches/0004-MC-Utils.patch b/Spigot-Server-Patches/0004-MC-Utils.patch index e34a6ed15..2a98b16b7 100644 --- a/Spigot-Server-Patches/0004-MC-Utils.patch +++ b/Spigot-Server-Patches/0004-MC-Utils.patch @@ -2096,31 +2096,19 @@ 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 new file mode 100644 -index 0000000000000000000000000000000000000000..9841212a60346870535e81b22851261e12380650 +index 0000000000000000000000000000000000000000..d0c77068e9a53d1b8bbad0f3f6b420d6bc85f8c8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java -@@ -0,0 +1,174 @@ +@@ -0,0 +1,85 @@ +package com.destroystokyo.paper.util.pooled; + +import net.minecraft.server.MCUtil; +import org.apache.commons.lang3.mutable.MutableInt; + +import java.util.ArrayDeque; -+import java.util.concurrent.ThreadLocalRandom; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; ++import java.util.function.Supplier; + -+/** -+ * Object pooling with thread safe, low contention design. Pooled objects have no additional object overhead -+ * due to usage of ArrayDeque per insertion/removal unless a resizing is needed in the buckets. -+ * Supports up to bucket size (default 8) threads concurrently accessing if all buckets have a value. -+ * Releasing may conditionally have contention if multiple buckets have same current size, but randomization will be used. -+ * -+ * Original interface API by Spottedleaf -+ * Implementation by Aikar -+ * @license MIT -+ */ +public final class PooledObjects { + + /** @@ -2144,43 +2132,28 @@ index 0000000000000000000000000000000000000000..9841212a60346870535e81b22851261e + } + } + -+ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024, 16); ++ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024); + -+ private final PooledObjectHandler handler; -+ private final int bucketCount; -+ private final int bucketSize; -+ private final ArrayDeque[] buckets; -+ private final ReentrantLock[] locks; -+ private final AtomicLong bucketIdCounter = new AtomicLong(0); ++ private final Supplier creator; ++ private final Consumer releaser; ++ private final int maxPoolSize; ++ private final ArrayDeque queue; + -+ public PooledObjects(final PooledObjectHandler handler, int maxPoolSize) { -+ this(handler, maxPoolSize, 8); ++ public PooledObjects(final Supplier creator, int maxPoolSize) { ++ this(creator, maxPoolSize, null); + } -+ public PooledObjects(final PooledObjectHandler handler, int maxPoolSize, int bucketCount) { -+ if (handler == null) { -+ throw new NullPointerException("Handler must not be null"); ++ public PooledObjects(final Supplier creator, int maxPoolSize, Consumer releaser) { ++ if (creator == null) { ++ throw new NullPointerException("Creator must not be null"); + } + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("Max pool size must be greater-than 0"); + } -+ if (bucketCount < 1) { -+ throw new IllegalArgumentException("Bucket count must be greater-than 0"); -+ } -+ int remainder = maxPoolSize % bucketCount; -+ if (remainder > 0) { -+ // Auto adjust up to the next bucket divisible size -+ maxPoolSize = maxPoolSize - remainder + bucketCount; -+ } -+ //noinspection unchecked -+ this.buckets = new ArrayDeque[bucketCount]; -+ this.locks = new ReentrantLock[bucketCount]; -+ this.bucketCount = bucketCount; -+ this.handler = handler; -+ this.bucketSize = maxPoolSize / bucketCount; -+ for (int i = 0; i < bucketCount; i++) { -+ this.buckets[i] = new ArrayDeque<>(bucketSize / 4); -+ this.locks[i] = new ReentrantLock(); -+ } ++ ++ this.queue = new ArrayDeque<>(maxPoolSize); ++ this.maxPoolSize = maxPoolSize; ++ this.creator = creator; ++ this.releaser = releaser; + } + + public AutoReleased acquireCleaner(Object holder) { @@ -2193,85 +2166,23 @@ index 0000000000000000000000000000000000000000..9841212a60346870535e81b22851261e + return new AutoReleased(resource, cleaner); + } + -+ -+ public long size() { -+ long size = 0; -+ for (int i = 0; i < bucketCount; i++) { -+ size += this.buckets[i].size(); ++ public final E acquire() { ++ E value; ++ synchronized (queue) { ++ value = this.queue.pollLast(); + } -+ -+ return size; ++ return value != null ? value : this.creator.get(); + } -+ public E acquire() { -+ for (int base = (int) (this.bucketIdCounter.getAndIncrement() % bucketCount), i = 0; i < bucketCount; i++ ) { -+ int bucketId = (base + i) % bucketCount; -+ if (this.buckets[bucketId].isEmpty()) continue; -+ // lock will alloc an object if blocked, so spinwait instead since lock duration is super fast -+ lockBucket(bucketId); -+ E value = this.buckets[bucketId].poll(); -+ this.locks[bucketId].unlock(); -+ if (value != null) { -+ this.handler.onAcquire(value); -+ return value; ++ ++ public final void release(final E value) { ++ if (this.releaser != null) { ++ this.releaser.accept(value); ++ } ++ synchronized (this.queue) { ++ if (queue.size() < this.maxPoolSize) { ++ this.queue.addLast(value); + } + } -+ return this.handler.createNew(); -+ } -+ -+ private void lockBucket(int bucketId) { -+ // lock will alloc an object if blocked, try to avoid unless 2 failures -+ ReentrantLock lock = this.locks[bucketId]; -+ if (!lock.tryLock()) { -+ Thread.yield(); -+ } else { -+ return; -+ } -+ if (!lock.tryLock()) { -+ Thread.yield(); -+ lock.lock(); -+ } -+ } -+ -+ public void release(final E value) { -+ int attempts = 3; // cap on contention -+ do { -+ // find least filled bucket before locking -+ int smallestIdx = -1; -+ int smallest = Integer.MAX_VALUE; -+ for (int i = 0; i < bucketCount; i++ ) { -+ ArrayDeque bucket = this.buckets[i]; -+ int size = bucket.size(); -+ if (size < this.bucketSize && (smallestIdx == -1 || size < smallest || (size == smallest && ThreadLocalRandom.current().nextBoolean()))) { -+ smallestIdx = i; -+ smallest = size; -+ } -+ } -+ if (smallestIdx == -1) return; // Can not find a bucket to fill -+ -+ lockBucket(smallestIdx); -+ ArrayDeque bucket = this.buckets[smallestIdx]; -+ if (bucket.size() < this.bucketSize) { -+ this.handler.onRelease(value); -+ bucket.push(value); -+ this.locks[smallestIdx].unlock(); -+ return; -+ } else { -+ this.locks[smallestIdx].unlock(); -+ } -+ } while (attempts-- > 0); -+ } -+ -+ /** This object is restricted from interacting with any pool */ -+ public interface PooledObjectHandler { -+ -+ /** -+ * Must return a non-null object -+ */ -+ E createNew(); -+ -+ default void onAcquire(final E value) {} -+ -+ default void onRelease(final E value) {} + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java diff --git a/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch index 6637a9bb8..87917a7f7 100644 --- a/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch +++ b/Spigot-Server-Patches/0522-Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -9,7 +9,7 @@ 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 e625842e524f18e469f7695b27d52d4d04892266..7c7b5212d8603627f260344a2fdcf575f81d7f63 100644 +index e625842e524f18e469f7695b27d52d4d04892266..49d95bb12083c306c8d257b202735066bad4388b 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -105,7 +105,11 @@ public class ChunkRegionLoader { @@ -20,7 +20,7 @@ index e625842e524f18e469f7695b27d52d4d04892266..7c7b5212d8603627f260344a2fdcf575 + // Pool safe get and clean + NBTTagByteArray blockLightArray = nbttagcompound2.getByteArrayTag("BlockLight"); + // NibbleArray will copy the data in the ctor -+ NibbleArray blockLight = new NibbleArray(blockLightArray.getBytesPoolSafe()); ++ NibbleArray blockLight = new NibbleArray().markPoolSafe().cloneAndSet(blockLightArray.getBytesPoolSafe()); // This is going to light engine which handles releasing + blockLightArray.cleanPooledBytes(); // Note: We move the block light nibble array creation here for perf & in case the compound is modified tasksToExecuteOnMain.add(() -> { @@ -33,7 +33,7 @@ index e625842e524f18e469f7695b27d52d4d04892266..7c7b5212d8603627f260344a2fdcf575 + // Pool safe get and clean + NBTTagByteArray skyLightArray = nbttagcompound2.getByteArrayTag("SkyLight"); + // NibbleArray will copy the data in the ctor -+ NibbleArray skyLight = new NibbleArray(skyLightArray.getBytesPoolSafe()); ++ NibbleArray skyLight = new NibbleArray().markPoolSafe().cloneAndSet(skyLightArray.getBytesPoolSafe()); // This is going to light engine which handles releasing + skyLightArray.cleanPooledBytes(); // Note: We move the block light nibble array creation here for perf & in case the compound is modified tasksToExecuteOnMain.add(() -> { @@ -57,9 +57,18 @@ index e625842e524f18e469f7695b27d52d4d04892266..7c7b5212d8603627f260344a2fdcf575 nbttaglist.add(nbttagcompound2); diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java -index 88277d23c36696fdd5363e41a130c9a443fac2c0..1a048cf586eac76499522599a0cac91e31472d72 100644 +index 88277d23c36696fdd5363e41a130c9a443fac2c0..fa8039d38d5b3110fd85abf850248ba7948374c3 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorage.java +++ b/src/main/java/net/minecraft/server/LightEngineStorage.java +@@ -148,7 +148,7 @@ public abstract class LightEngineStorage> 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) { @@ -319,7 +319,7 @@ public abstract class LightEngineStorage> e if (nibblearray != null) { this.i.put(i, nibblearray); @@ -69,19 +78,35 @@ index 88277d23c36696fdd5363e41a130c9a443fac2c0..1a048cf586eac76499522599a0cac91e } } +diff --git a/src/main/java/net/minecraft/server/LightEngineStorageArray.java b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +index 278aec8846d3bd448e359095063a711e78213ee5..f17b16d5c52cd77dd53807222dff4631d185e159 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +@@ -27,7 +27,7 @@ public abstract class LightEngineStorageArray BYTE_2048 = new PooledObjects<>(() -> new byte[2048], maxPoolSize, 8); ++ public static final PooledObjects 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); @@ -193,6 +218,14 @@ index 996c8326387b5a7fe62db6a76e000144565cb85b..1fcb1bdab28f79320aef50a9bbb2fbee + } + } + ++ 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; + } @@ -204,8 +237,23 @@ index 996c8326387b5a7fe62db6a76e000144565cb85b..1fcb1bdab28f79320aef50a9bbb2fbee + 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() { cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); } ++ 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; + @@ -224,7 +272,7 @@ index 996c8326387b5a7fe62db6a76e000144565cb85b..1fcb1bdab28f79320aef50a9bbb2fbee if (abyte.length != 2048) { throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length)); } -@@ -44,7 +78,8 @@ public class NibbleArray { +@@ -44,7 +101,8 @@ public class NibbleArray { public void a(int i, int j) { // PAIL: private -> public if (this.a == null) { @@ -234,7 +282,7 @@ index 996c8326387b5a7fe62db6a76e000144565cb85b..1fcb1bdab28f79320aef50a9bbb2fbee } int k = this.d(i); -@@ -65,7 +100,8 @@ public class NibbleArray { +@@ -65,7 +123,8 @@ public class NibbleArray { public byte[] asBytes() { if (this.a == null) { @@ -244,7 +292,7 @@ index 996c8326387b5a7fe62db6a76e000144565cb85b..1fcb1bdab28f79320aef50a9bbb2fbee } return this.a; -@@ -73,7 +109,7 @@ public class NibbleArray { +@@ -73,7 +132,7 @@ public class NibbleArray { public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER public NibbleArray b() {