Skip to content

Commit 7eaa868

Browse files
authored
Reduce indirection in AbstractRefCounted (#96403)
Today `AbstractRefCounted` holds an `AtomicInteger` which holds the actual ref count, which is an extra heap object and means that acquiring/releasing refs always goes through that extra pointer lookup. We use this utility extensively, on some pretty hot paths, so with this commit we move to using a primitive `refCount` field with atomic operations via a `VarHandle`.
1 parent 06f0276 commit 7eaa868

File tree

2 files changed

+28
-10
lines changed

2 files changed

+28
-10
lines changed

libs/core/src/main/java/org/elasticsearch/core/AbstractRefCounted.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,32 @@
88

99
package org.elasticsearch.core;
1010

11+
import java.lang.invoke.MethodHandles;
12+
import java.lang.invoke.VarHandle;
1113
import java.util.Objects;
12-
import java.util.concurrent.atomic.AtomicInteger;
1314

1415
/**
1516
* A basic {@link RefCounted} implementation that is initialized with a ref count of 1 and calls {@link #closeInternal()} once it reaches
1617
* a 0 ref count.
1718
*/
1819
public abstract class AbstractRefCounted implements RefCounted {
20+
1921
public static final String ALREADY_CLOSED_MESSAGE = "already closed, can't increment ref count";
2022

21-
private final AtomicInteger refCount = new AtomicInteger(1);
23+
private static final VarHandle VH_REFCOUNT_FIELD;
24+
25+
static {
26+
try {
27+
VH_REFCOUNT_FIELD = MethodHandles.lookup()
28+
.in(AbstractRefCounted.class)
29+
.findVarHandle(AbstractRefCounted.class, "refCount", int.class);
30+
} catch (NoSuchFieldException | IllegalAccessException e) {
31+
throw new RuntimeException(e);
32+
}
33+
}
34+
35+
@SuppressWarnings("FieldMayBeFinal") // updated via VH_REFCOUNT_FIELD (and _only_ via VH_REFCOUNT_FIELD)
36+
private volatile int refCount = 1;
2237

2338
protected AbstractRefCounted() {}
2439

@@ -32,9 +47,9 @@ public final void incRef() {
3247
@Override
3348
public final boolean tryIncRef() {
3449
do {
35-
int i = refCount.get();
50+
int i = refCount;
3651
if (i > 0) {
37-
if (refCount.compareAndSet(i, i + 1)) {
52+
if (VH_REFCOUNT_FIELD.weakCompareAndSet(this, i, i + 1)) {
3853
touch();
3954
return true;
4055
}
@@ -47,9 +62,9 @@ public final boolean tryIncRef() {
4762
@Override
4863
public final boolean decRef() {
4964
touch();
50-
int i = refCount.decrementAndGet();
51-
assert i >= 0 : "invalid decRef call: already closed";
52-
if (i == 0) {
65+
int i = (int) VH_REFCOUNT_FIELD.getAndAdd(this, -1);
66+
assert i > 0 : "invalid decRef call: already closed";
67+
if (i == 1) {
5368
try {
5469
closeInternal();
5570
} catch (Exception e) {
@@ -63,7 +78,7 @@ public final boolean decRef() {
6378

6479
@Override
6580
public final boolean hasReferences() {
66-
return refCount.get() > 0;
81+
return refCount > 0;
6782
}
6883

6984
/**
@@ -73,7 +88,7 @@ public final boolean hasReferences() {
7388
protected void touch() {}
7489

7590
protected void alreadyClosed() {
76-
final int currentRefCount = refCount.get();
91+
final int currentRefCount = refCount;
7792
assert currentRefCount == 0 : currentRefCount;
7893
throw new IllegalStateException(ALREADY_CLOSED_MESSAGE);
7994
}
@@ -82,7 +97,7 @@ protected void alreadyClosed() {
8297
* Returns the current reference count.
8398
*/
8499
public final int refCount() {
85-
return this.refCount.get();
100+
return refCount;
86101
}
87102

88103
/**

server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.settings.SecureSettings;
2525
import org.elasticsearch.common.settings.Settings;
2626
import org.elasticsearch.common.transport.BoundTransportAddress;
27+
import org.elasticsearch.core.AbstractRefCounted;
2728
import org.elasticsearch.core.IOUtils;
2829
import org.elasticsearch.core.SuppressForbidden;
2930
import org.elasticsearch.env.Environment;
@@ -181,6 +182,8 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
181182
try {
182183
// ReferenceDocs class does nontrivial static initialization which should always succeed but load it now (before SM) to be sure
183184
MethodHandles.publicLookup().ensureInitialized(ReferenceDocs.class);
185+
// AbstractRefCounted class uses MethodHandles.lookup during initialization, load it now (before SM) to be sure it succeeds
186+
MethodHandles.publicLookup().ensureInitialized(AbstractRefCounted.class);
184187
} catch (IllegalAccessException unexpected) {
185188
throw new AssertionError(unexpected);
186189
}

0 commit comments

Comments
 (0)