Skip to content

Commit d11df1a

Browse files
committed
Pad the drain status field
Profiling shows that this field is hot, especially after the last change which avoids the maintenance penalty on calling threads. By padding this field the read performance is increased due to avoiding false sharing of the cache line.
1 parent 1bbfc86 commit d11df1a

File tree

7 files changed

+72
-43
lines changed

7 files changed

+72
-43
lines changed

caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected Buffer<E> create(E e) {
5757
return new RingBuffer<>(e);
5858
}
5959

60-
static final class RingBuffer<E> extends ReadAndWriteCounterRef implements Buffer<E> {
60+
static final class RingBuffer<E> extends BBHeader.ReadAndWriteCounterRef implements Buffer<E> {
6161
final AtomicReference<E>[] buffer;
6262

6363
@SuppressWarnings({"unchecked", "cast", "rawtypes"})
@@ -121,44 +121,48 @@ public int writes() {
121121
}
122122
}
123123

124-
abstract class PadReadCounter {
125-
long p00, p01, p02, p03, p04, p05, p06, p07;
126-
long p30, p31, p32, p33, p34, p35, p36, p37;
127-
}
124+
/** The namespace for field padding through inheritance. */
125+
final class BBHeader {
126+
127+
static abstract class PadReadCounter {
128+
long p00, p01, p02, p03, p04, p05, p06, p07;
129+
long p30, p31, p32, p33, p34, p35, p36, p37;
130+
}
128131

129-
/** Enforces a memory layout to avoid false sharing by padding the read count. */
130-
abstract class ReadCounterRef extends PadReadCounter {
131-
static final long READ_OFFSET =
132-
UnsafeAccess.objectFieldOffset(ReadCounterRef.class, "readCounter");
132+
/** Enforces a memory layout to avoid false sharing by padding the read count. */
133+
static abstract class ReadCounterRef extends PadReadCounter {
134+
static final long READ_OFFSET =
135+
UnsafeAccess.objectFieldOffset(ReadCounterRef.class, "readCounter");
133136

134-
volatile long readCounter;
137+
volatile long readCounter;
135138

136-
void lazySetReadCounter(long count) {
137-
UnsafeAccess.UNSAFE.putOrderedLong(this, READ_OFFSET, count);
139+
void lazySetReadCounter(long count) {
140+
UnsafeAccess.UNSAFE.putOrderedLong(this, READ_OFFSET, count);
141+
}
138142
}
139-
}
140143

141-
abstract class PadWriteCounter extends ReadCounterRef {
142-
long p00, p01, p02, p03, p04, p05, p06, p07;
143-
long p30, p31, p32, p33, p34, p35, p36, p37;
144-
}
144+
static abstract class PadWriteCounter extends ReadCounterRef {
145+
long p00, p01, p02, p03, p04, p05, p06, p07;
146+
long p30, p31, p32, p33, p34, p35, p36, p37;
147+
}
145148

146-
/** Enforces a memory layout to avoid false sharing by padding the write count. */
147-
abstract class ReadAndWriteCounterRef extends PadWriteCounter {
148-
static final long WRITE_OFFSET =
149-
UnsafeAccess.objectFieldOffset(ReadAndWriteCounterRef.class, "writeCounter");
149+
/** Enforces a memory layout to avoid false sharing by padding the write count. */
150+
static abstract class ReadAndWriteCounterRef extends PadWriteCounter {
151+
static final long WRITE_OFFSET =
152+
UnsafeAccess.objectFieldOffset(ReadAndWriteCounterRef.class, "writeCounter");
150153

151-
volatile long writeCounter;
154+
volatile long writeCounter;
152155

153-
ReadAndWriteCounterRef(int writes) {
154-
UnsafeAccess.UNSAFE.putOrderedLong(this, WRITE_OFFSET, writes);
155-
}
156+
ReadAndWriteCounterRef(int writes) {
157+
UnsafeAccess.UNSAFE.putOrderedLong(this, WRITE_OFFSET, writes);
158+
}
156159

157-
long relaxedTail() {
158-
return UnsafeAccess.UNSAFE.getLong(this, WRITE_OFFSET);
159-
}
160+
long relaxedTail() {
161+
return UnsafeAccess.UNSAFE.getLong(this, WRITE_OFFSET);
162+
}
160163

161-
boolean casWriteCounter(long expect, long update) {
162-
return UnsafeAccess.UNSAFE.compareAndSwapLong(this, WRITE_OFFSET, expect, update);
164+
boolean casWriteCounter(long expect, long update) {
165+
return UnsafeAccess.UNSAFE.compareAndSwapLong(this, WRITE_OFFSET, expect, update);
166+
}
163167
}
164168
}

caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import java.util.concurrent.Executor;
4949
import java.util.concurrent.ForkJoinPool;
5050
import java.util.concurrent.TimeUnit;
51-
import java.util.concurrent.atomic.AtomicReference;
5251
import java.util.concurrent.locks.Lock;
5352
import java.util.concurrent.locks.ReentrantLock;
5453
import java.util.function.BiFunction;
@@ -61,6 +60,8 @@
6160
import javax.annotation.concurrent.GuardedBy;
6261
import javax.annotation.concurrent.ThreadSafe;
6362

63+
import com.github.benmanes.caffeine.base.UnsafeAccess;
64+
import com.github.benmanes.caffeine.cache.BoundedLocalCache.DrainStatus;
6465
import com.github.benmanes.caffeine.cache.References.InternalReference;
6566
import com.github.benmanes.caffeine.cache.stats.DisabledStatsCounter;
6667
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
@@ -80,7 +81,8 @@
8081
* @param <V> the type of mapped values
8182
*/
8283
@ThreadSafe
83-
abstract class BoundedLocalCache<K, V> extends AbstractMap<K, V> implements LocalCache<K, V> {
84+
abstract class BoundedLocalCache<K, V> extends BLCHeader.DrainStatusRef<K, V>
85+
implements LocalCache<K, V> {
8486

8587
/*
8688
* This class performs a best-effort bounding of a ConcurrentHashMap using a page-replacement
@@ -119,7 +121,6 @@ abstract class BoundedLocalCache<K, V> extends AbstractMap<K, V> implements Loca
119121
static final long MAXIMUM_CAPACITY = Long.MAX_VALUE - Integer.MAX_VALUE;
120122

121123
final ConcurrentHashMap<Object, Node<K, V>> data;
122-
final AtomicReference<DrainStatus> drainStatus;
123124
final Consumer<Node<K, V>> accessPolicy;
124125
final Buffer<Node<K, V>> readBuffer;
125126
final Runnable drainBuffersTask;
@@ -139,7 +140,6 @@ protected BoundedLocalCache(Caffeine<K, V> builder, boolean isAsync) {
139140
this.isAsync = isAsync;
140141
weigher = builder.getWeigher(isAsync);
141142
id = tracer().register(builder.name());
142-
drainStatus = new AtomicReference<DrainStatus>(IDLE);
143143
data = new ConcurrentHashMap<>(builder.getInitialCapacity());
144144
evictionLock = builder.hasExecutor() ? new ReentrantLock() : new NonReentrantLock();
145145
nodeFactory = NodeFactory.getFactory(builder.isStrongKeys(), builder.isWeakKeys(),
@@ -495,7 +495,7 @@ void refreshIfNeeded(Node<K, V> node, long now) {
495495
* @param delayable if draining the read buffer can be delayed
496496
*/
497497
void drainOnReadIfNeeded(boolean delayable) {
498-
final DrainStatus status = drainStatus.get();
498+
final DrainStatus status = drainStatus;
499499
if (status.shouldDrainBuffers(delayable)) {
500500
scheduleDrainBuffers();
501501
}
@@ -516,7 +516,7 @@ void afterWrite(@Nullable Node<K, V> node, Runnable task) {
516516
if (buffersWrites()) {
517517
writeQueue().add(task);
518518
}
519-
drainStatus.lazySet(REQUIRED);
519+
lazySetDrainStatus(REQUIRED);
520520
scheduleDrainBuffers();
521521
}
522522

@@ -527,7 +527,7 @@ void afterWrite(@Nullable Node<K, V> node, Runnable task) {
527527
void scheduleDrainBuffers() {
528528
if (evictionLock.tryLock()) {
529529
try {
530-
drainStatus.lazySet(PROCESSING);
530+
lazySetDrainStatus(PROCESSING);
531531
executor().execute(drainBuffersTask);
532532
} catch (Throwable t) {
533533
cleanUp();
@@ -542,10 +542,10 @@ void scheduleDrainBuffers() {
542542
public void cleanUp() {
543543
evictionLock.lock();
544544
try {
545-
drainStatus.lazySet(PROCESSING);
545+
lazySetDrainStatus(PROCESSING);
546546
maintenance();
547547
} finally {
548-
drainStatus.compareAndSet(PROCESSING, IDLE);
548+
casDrainStatus(PROCESSING, IDLE);
549549
evictionLock.unlock();
550550
}
551551
}
@@ -1933,3 +1933,28 @@ Object writeReplace() {
19331933
}
19341934
}
19351935
}
1936+
1937+
/** The namespace for field padding through inheritance. */
1938+
final class BLCHeader {
1939+
1940+
static abstract class PadDrainStatus<K, V> extends AbstractMap<K, V> {
1941+
long p00, p01, p02, p03, p04, p05, p06, p07;
1942+
long p30, p31, p32, p33, p34, p35, p36, p37;
1943+
}
1944+
1945+
/** Enforces a memory layout to avoid false sharing by padding the drain status. */
1946+
static abstract class DrainStatusRef<K, V> extends PadDrainStatus<K, V> {
1947+
static final long DRAIN_STATUS_OFFSET =
1948+
UnsafeAccess.objectFieldOffset(DrainStatusRef.class, "drainStatus");
1949+
1950+
volatile DrainStatus drainStatus = IDLE;
1951+
1952+
void lazySetDrainStatus(DrainStatus drainStatus) {
1953+
UnsafeAccess.UNSAFE.putOrderedObject(this, DRAIN_STATUS_OFFSET, drainStatus);
1954+
}
1955+
1956+
boolean casDrainStatus(DrainStatus expect, DrainStatus update) {
1957+
return UnsafeAccess.UNSAFE.compareAndSwapObject(this, DRAIN_STATUS_OFFSET, expect, update);
1958+
}
1959+
}
1960+
}

caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public void drain_nonblocking(Cache<Integer, Integer> cache) {
316316
BoundedLocalCache<Integer, Integer> localCache = asBoundedLocalCache(cache);
317317
AtomicBoolean done = new AtomicBoolean();
318318
Runnable task = () -> {
319-
localCache.drainStatus.lazySet(DrainStatus.REQUIRED);
319+
localCache.lazySetDrainStatus(DrainStatus.REQUIRED);
320320
localCache.scheduleDrainBuffers();
321321
done.set(true);
322322
};
@@ -360,7 +360,7 @@ void checkDrainBlocks(BoundedLocalCache<Integer, Integer> localCache, Runnable t
360360
lock.lock();
361361
try {
362362
executor.execute(() -> {
363-
localCache.drainStatus.lazySet(DrainStatus.REQUIRED);
363+
localCache.lazySetDrainStatus(DrainStatus.REQUIRED);
364364
task.run();
365365
done.set(true);
366366
});

caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public void run() {
9898
System.out.printf("---------- %s ----------%n", elapsedTime);
9999
System.out.printf("Pending reads = %s%n", pendingReads);
100100
System.out.printf("Pending write = %s%n", pendingWrites);
101-
System.out.printf("Drain status = %s%n", local.drainStatus.get());
101+
System.out.printf("Drain status = %s%n", local.drainStatus);
102102
System.out.printf("Evictions = %,d%n", evictions.intValue());
103103
System.out.printf("Lock = %s%n", local.evictionLock);
104104
}

gradle/dependencies.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ext {
3434
jcache: '1.0.0',
3535
joor: '0.9.5',
3636
jsr305: '3.0.0',
37-
univocity_parsers: '1.5.2',
37+
univocity_parsers: '1.5.4',
3838
]
3939
test_versions = [
4040
awaitility: '1.6.3',

wiki/read.png

-5.32 KB
Loading

wiki/readwrite.png

-5.67 KB
Loading

0 commit comments

Comments
 (0)