Skip to content

Commit f12e40e

Browse files
committed
Atomics benchmark
Benchmark to show cost of Atomic vs AtomicFieldUpdater
1 parent 31dc270 commit f12e40e

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package datadog.trace.util;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
5+
import java.util.function.Supplier;
6+
7+
import org.openjdk.jmh.annotations.Benchmark;
8+
import org.openjdk.jmh.annotations.Fork;
9+
import org.openjdk.jmh.annotations.Measurement;
10+
import org.openjdk.jmh.annotations.Threads;
11+
import org.openjdk.jmh.annotations.Warmup;
12+
13+
/**
14+
* <ul>
15+
* <li>(RECOMMENDED) AtomicFieldUpdater - especially, when containing object is frequently constructed
16+
* <li>Atomic - usually, performs similarly to AtomicFieldUpdater - worse inside commonly constructed objects
17+
* </ul>
18+
*
19+
* Instead of introucing an Atomic field into a class, a volatile field with an AtomicFieldUpdater is preferred when possible.
20+
* <ul>Types with AtomicFieldUpdaters are...
21+
* <li>int
22+
* <li>long
23+
* <li>reference (e.g. Object types)
24+
* </ul>
25+
*
26+
* While the performance of Atomic is on par with AtomicFieldUpdater (and sometimes slightly better) inside a frequently
27+
* constructed object, the impact of the extra allocation on garbage collection is detrimental to the application as a whole.
28+
*
29+
* <code> Java 17 - MacBook M1 - 8 threads
30+
* Benchmark Mode Cnt Score Error Units
31+
* AtomicsBenchmark.atomicFieldUpdater_construction thrpt 6 4442811623.924 ± 293568588.782 ops/s
32+
* AtomicsBenchmark.atomicFieldUpdater_construction:gc.alloc.rate.norm thrpt 6 16.000 ± 0.001 B/op
33+
34+
* AtomicsBenchmark.atomicFieldUpdater_get thrpt 6 1849238178.083 ± 29719143.488 ops/s
35+
* AtomicsBenchmark.atomicFieldUpdater_get:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op
36+
37+
* AtomicsBenchmark.atomicFieldUpdater_getVolatile thrpt 6 1814698391.230 ± 54378746.492 ops/s
38+
* AtomicsBenchmark.atomicFieldUpdater_getVolatile:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op
39+
40+
* AtomicsBenchmark.atomicFieldUpdater_incrementAndGet thrpt 6 15871753.651 ± 432451.444 ops/s
41+
* AtomicsBenchmark.atomicFieldUpdater_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op
42+
43+
* AtomicsBenchmark.atomic_construction thrpt 6 2532095943.171 ± 87644803.894 ops/s
44+
* AtomicsBenchmark.atomic_construction:gc.alloc.rate.norm thrpt 6 32.000 ± 0.001 B/op
45+
46+
* AtomicsBenchmark.atomic_incrementAndGet thrpt 6 16416635.546 ± 751137.083 ops/s
47+
* AtomicsBenchmark.atomic_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op
48+
49+
* AtomicsBenchmark.atomic_read thrpt 6 1943297600.062 ± 43802387.143 ops/s
50+
* AtomicsBenchmark.atomic_read:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op
51+
*/
52+
@Fork(2)
53+
@Warmup(iterations=2)
54+
@Measurement(iterations=3)
55+
@Threads(8)
56+
public class AtomicsBenchmark {
57+
static int SIZE = 32;
58+
59+
static final class AtomicHolder {
60+
final AtomicInteger atomic;
61+
62+
AtomicHolder(int num) {
63+
this.atomic = new AtomicInteger(num);
64+
}
65+
66+
int get() {
67+
return this.atomic.get();
68+
}
69+
70+
int incrementAndGet() {
71+
return this.atomic.incrementAndGet();
72+
}
73+
}
74+
75+
static final class FieldHolder {
76+
static final AtomicIntegerFieldUpdater<FieldHolder> AFU_FIELD = AtomicIntegerFieldUpdater.newUpdater(FieldHolder.class, "field");
77+
78+
volatile int field;
79+
80+
FieldHolder(int num) {
81+
this.field = num;
82+
}
83+
84+
int getVolatile() {
85+
return this.field;
86+
}
87+
88+
int get() {
89+
return AFU_FIELD.get(this);
90+
}
91+
92+
int incrementAndGet() {
93+
return AFU_FIELD.incrementAndGet(this);
94+
}
95+
}
96+
97+
static final AtomicHolder[] atomicHolders = init(() -> {
98+
AtomicHolder[] holders = new AtomicHolder[SIZE];
99+
for ( int i = 0; i < holders.length; ++i ) {
100+
holders[i] = new AtomicHolder(i * 2);
101+
}
102+
return holders;
103+
});
104+
105+
static final FieldHolder[] fieldHolders = init(() -> {
106+
FieldHolder[] holders = new FieldHolder[SIZE];
107+
for ( int i = 0; i < holders.length; ++i ) {
108+
holders[i] = new FieldHolder(i * 2);
109+
}
110+
return holders;
111+
});
112+
113+
static final <T> T init(Supplier<T> supplier) {
114+
return supplier.get();
115+
}
116+
117+
static int sharedLookupIndex = 0;
118+
119+
static <T> T next(T[] holders) {
120+
int localIndex = ++sharedLookupIndex;
121+
if ( localIndex >= holders.length ) {
122+
sharedLookupIndex = localIndex = 0;
123+
}
124+
return holders[localIndex];
125+
}
126+
127+
@Benchmark
128+
public Object atomic_construction() {
129+
return new AtomicHolder(0);
130+
}
131+
132+
@Benchmark
133+
public int atomic_incrementAndGet() {
134+
return next(atomicHolders).incrementAndGet();
135+
}
136+
137+
@Benchmark
138+
public Object atomic_read() {
139+
return next(atomicHolders).get();
140+
}
141+
142+
@Benchmark
143+
public Object atomicFieldUpdater_construction() {
144+
return new FieldHolder(0);
145+
}
146+
147+
@Benchmark
148+
public Object atomicFieldUpdater_getVolatile() {
149+
return next(fieldHolders).getVolatile();
150+
}
151+
152+
@Benchmark
153+
public Object atomicFieldUpdater_get() {
154+
return next(fieldHolders).get();
155+
}
156+
157+
@Benchmark
158+
public int atomicFieldUpdater_incrementAndGet() {
159+
return next(fieldHolders).incrementAndGet();
160+
}
161+
}

0 commit comments

Comments
 (0)