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
This commit is contained in:
Nick Hill 2019-11-04 02:24:20 -08:00 committed by Norman Maurer
parent f0e1628426
commit d3011d4f5b
2 changed files with 20 additions and 15 deletions

View File

@ -103,14 +103,14 @@ public abstract class Recycler<T> {
private final int maxCapacityPerThread; private final int maxCapacityPerThread;
private final int maxSharedCapacityFactor; private final int maxSharedCapacityFactor;
private final int ratioMask; private final int interval;
private final int maxDelayedQueuesPerThread; private final int maxDelayedQueuesPerThread;
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() { private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
@Override @Override
protected Stack<T> initialValue() { protected Stack<T> initialValue() {
return new Stack<>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor, return new Stack<>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
ratioMask, maxDelayedQueuesPerThread); interval, maxDelayedQueuesPerThread);
} }
@Override @Override
@ -138,7 +138,7 @@ public abstract class Recycler<T> {
protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor, protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor,
int ratio, int maxDelayedQueuesPerThread) { int ratio, int maxDelayedQueuesPerThread) {
ratioMask = safeFindNextPositivePowerOfTwo(ratio) - 1; interval = safeFindNextPositivePowerOfTwo(ratio);
if (maxCapacityPerThread <= 0) { if (maxCapacityPerThread <= 0) {
this.maxCapacityPerThread = 0; this.maxCapacityPerThread = 0;
this.maxSharedCapacityFactor = 1; this.maxSharedCapacityFactor = 1;
@ -310,13 +310,13 @@ public abstract class Recycler<T> {
// pointer to another queue of delayed items for the same stack // pointer to another queue of delayed items for the same stack
private WeakOrderQueue next; private WeakOrderQueue next;
private final int id = ID_GENERATOR.getAndIncrement(); private final int id = ID_GENERATOR.getAndIncrement();
private final int ratioMask; private final int interval;
private int handleRecycleCount; private int handleRecycleCount;
private WeakOrderQueue() { private WeakOrderQueue() {
super(null); super(null);
head = new Head(null); head = new Head(null);
ratioMask = 0; interval = 0;
} }
private WeakOrderQueue(Stack<?> stack, Thread thread) { private WeakOrderQueue(Stack<?> stack, Thread thread) {
@ -328,7 +328,8 @@ public abstract class Recycler<T> {
// Stack itself GCed. // Stack itself GCed.
head = new Head(stack.availableSharedCapacity); head = new Head(stack.availableSharedCapacity);
head.link = tail; head.link = tail;
ratioMask = stack.ratioMask; interval = stack.interval;
handleRecycleCount = interval; // Start at interval so the first one will be recycled.
} }
static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) { static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
@ -364,10 +365,12 @@ public abstract class Recycler<T> {
// While we also enforce the recycling ratio one we transfer objects from the WeakOrderQueue to the Stack // 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 // we better should enforce it as well early. Missing to do so may let the WeakOrderQueue grow very fast
// without control if the Stack // without control if the Stack
if ((++handleRecycleCount & ratioMask) != 0) { if (handleRecycleCount < interval) {
handleRecycleCount++;
// Drop the item to prevent recycling to aggressive. // Drop the item to prevent recycling to aggressive.
return; return;
} }
handleRecycleCount = 0;
Link tail = this.tail; Link tail = this.tail;
int writeIndex; int writeIndex;
@ -482,21 +485,22 @@ public abstract class Recycler<T> {
private final int maxDelayedQueues; private final int maxDelayedQueues;
private final int maxCapacity; private final int maxCapacity;
private final int ratioMask; private final int interval;
DefaultHandle<?>[] elements; DefaultHandle<?>[] elements;
int size; int size;
private int handleRecycleCount = -1; // Start with -1 so the first one will be recycled. private int handleRecycleCount;
private WeakOrderQueue cursor, prev; private WeakOrderQueue cursor, prev;
private volatile WeakOrderQueue head; private volatile WeakOrderQueue head;
Stack(Recycler<T> parent, Thread thread, int maxCapacity, int maxSharedCapacityFactor, Stack(Recycler<T> parent, Thread thread, int maxCapacity, int maxSharedCapacityFactor,
int ratioMask, int maxDelayedQueues) { int interval, int maxDelayedQueues) {
this.parent = parent; this.parent = parent;
threadRef = new WeakReference<>(thread); threadRef = new WeakReference<>(thread);
this.maxCapacity = maxCapacity; this.maxCapacity = maxCapacity;
availableSharedCapacity = new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY)); availableSharedCapacity = new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY));
elements = new DefaultHandle[min(INITIAL_CAPACITY, maxCapacity)]; 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; this.maxDelayedQueues = maxDelayedQueues;
} }
@ -686,10 +690,12 @@ public abstract class Recycler<T> {
boolean dropHandle(DefaultHandle<?> handle) { boolean dropHandle(DefaultHandle<?> handle) {
if (!handle.hasBeenRecycled) { if (!handle.hasBeenRecycled) {
if ((++handleRecycleCount & ratioMask) != 0) { if (handleRecycleCount < interval) {
handleRecycleCount++;
// Drop the object. // Drop the object.
return true; return true;
} }
handleRecycleCount = 0;
handle.hasBeenRecycled = true; handle.hasBeenRecycled = true;
} }
return false; return false;

View File

@ -174,9 +174,8 @@ public class RecyclerTest {
thread.start(); thread.start();
thread.join(); 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(), o);
assertSame(recycler.get(), o2); assertNotSame(recycler.get(), o2);
assertNotSame(recycler.get(), o);
} }
@Test @Test