diff --git a/Spigot-Server-Patches/MC-Utils.patch b/Spigot-Server-Patches/MC-Utils.patch index 641da437e..63df3a525 100644 --- a/Spigot-Server-Patches/MC-Utils.patch +++ b/Spigot-Server-Patches/MC-Utils.patch @@ -2106,21 +2106,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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..00000000000000000000000000000000 + } + } + -+ 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..00000000000000000000000000000000 + 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/Optimize-NibbleArray-to-use-pooled-buffers.patch b/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch index 4fa16cd13..000665af0 100644 --- a/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch +++ b/Spigot-Server-Patches/Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -20,7 +20,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // 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(() -> { @@ -60,6 +60,15 @@ diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/ma index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorage.java +++ b/src/main/java/net/minecraft/server/LightEngineStorage.java +@@ -0,0 +0,0 @@ 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) { @@ -0,0 +0,0 @@ public abstract class LightEngineStorage> e if (nibblearray != null) { this.i.put(i, nibblearray); @@ -69,6 +78,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } } +diff --git a/src/main/java/net/minecraft/server/LightEngineStorageArray.java b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + ++ 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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; +