From 17bffce90e2051250be7eea85a3e327717a6aa7f Mon Sep 17 00:00:00 2001 From: Nick Hill Date: Mon, 4 Nov 2019 02:24:20 -0800 Subject: [PATCH] Use interval instead of mask comparison for Recycler ratio (#9748) Motivation The recycling ratio is currently implemented by comparing with a masked count. The mask operation is not free and also not necessary. Modification Change the count(s) to just iterate over the corresponding interval, which requires only a comparison and no mask. Also make "first time recycle" behaviour consistent and revert change to RecyclerTest made in #9727. Result Less recycling overhead --- .../src/main/java/io/netty/util/Recycler.java | 30 +++++++++++-------- .../test/java/io/netty/util/RecyclerTest.java | 5 ++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/io/netty/util/Recycler.java b/common/src/main/java/io/netty/util/Recycler.java index 15123cb44a..d73e38c1ce 100644 --- a/common/src/main/java/io/netty/util/Recycler.java +++ b/common/src/main/java/io/netty/util/Recycler.java @@ -106,14 +106,14 @@ public abstract class Recycler { private final int maxCapacityPerThread; private final int maxSharedCapacityFactor; - private final int ratioMask; + private final int interval; private final int maxDelayedQueuesPerThread; private final FastThreadLocal> threadLocal = new FastThreadLocal>() { @Override protected Stack initialValue() { return new Stack(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor, - ratioMask, maxDelayedQueuesPerThread); + interval, maxDelayedQueuesPerThread); } @Override @@ -141,7 +141,7 @@ public abstract class Recycler { protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor, int ratio, int maxDelayedQueuesPerThread) { - ratioMask = safeFindNextPositivePowerOfTwo(ratio) - 1; + interval = safeFindNextPositivePowerOfTwo(ratio); if (maxCapacityPerThread <= 0) { this.maxCapacityPerThread = 0; this.maxSharedCapacityFactor = 1; @@ -306,13 +306,13 @@ public abstract class Recycler { // pointer to another queue of delayed items for the same stack private WeakOrderQueue next; private final int id = ID_GENERATOR.getAndIncrement(); - private final int ratioMask; + private final int interval; private int handleRecycleCount; private WeakOrderQueue() { super(null); head = new Head(null); - ratioMask = 0; + interval = 0; } private WeakOrderQueue(Stack stack, Thread thread) { @@ -324,7 +324,8 @@ public abstract class Recycler { // Stack itself GCed. head = new Head(stack.availableSharedCapacity); head.link = tail; - ratioMask = stack.ratioMask; + interval = stack.interval; + handleRecycleCount = interval; // Start at interval so the first one will be recycled. } private static WeakOrderQueue newQueue(Stack stack, Thread thread) { @@ -365,10 +366,12 @@ public abstract class Recycler { // While we also enforce the recycling ratio one we transfer objects from the WeakOrderQueue to the Stack // we better should enforce it as well early. Missing to do so may let the WeakOrderQueue grow very fast // without control if the Stack - if ((++handleRecycleCount & ratioMask) != 0) { + if (handleRecycleCount < interval) { + handleRecycleCount++; // Drop the item to prevent recycling to aggressive. return; } + handleRecycleCount = 0; Link tail = this.tail; int writeIndex; @@ -483,21 +486,22 @@ public abstract class Recycler { private final int maxDelayedQueues; private final int maxCapacity; - private final int ratioMask; + private final int interval; DefaultHandle[] elements; int size; - private int handleRecycleCount = -1; // Start with -1 so the first one will be recycled. + private int handleRecycleCount; private WeakOrderQueue cursor, prev; private volatile WeakOrderQueue head; Stack(Recycler parent, Thread thread, int maxCapacity, int maxSharedCapacityFactor, - int ratioMask, int maxDelayedQueues) { + int interval, int maxDelayedQueues) { this.parent = parent; threadRef = new WeakReference(thread); this.maxCapacity = maxCapacity; availableSharedCapacity = new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY)); elements = new DefaultHandle[min(INITIAL_CAPACITY, maxCapacity)]; - this.ratioMask = ratioMask; + this.interval = interval; + handleRecycleCount = interval; // Start at interval so the first one will be recycled. this.maxDelayedQueues = maxDelayedQueues; } @@ -680,10 +684,12 @@ public abstract class Recycler { boolean dropHandle(DefaultHandle handle) { if (!handle.hasBeenRecycled) { - if ((++handleRecycleCount & ratioMask) != 0) { + if (handleRecycleCount < interval) { + handleRecycleCount++; // Drop the object. return true; } + handleRecycleCount = 0; handle.hasBeenRecycled = true; } return false; diff --git a/common/src/test/java/io/netty/util/RecyclerTest.java b/common/src/test/java/io/netty/util/RecyclerTest.java index 5369c12e21..8eaef8d324 100644 --- a/common/src/test/java/io/netty/util/RecyclerTest.java +++ b/common/src/test/java/io/netty/util/RecyclerTest.java @@ -185,9 +185,8 @@ public class RecyclerTest { thread.start(); thread.join(); - // As we use a ratioMask of 2 we should see o2 as the first object that could recycled from a different thread. - assertSame(recycler.get(), o2); - assertNotSame(recycler.get(), o); + assertSame(recycler.get(), o); + assertNotSame(recycler.get(), o2); } @Test