From f12e40ed496256492db7915792de4d136c1a0ecb Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 16 Mar 2026 14:43:35 -0400 Subject: [PATCH 1/2] Atomics benchmark Benchmark to show cost of Atomic vs AtomicFieldUpdater --- .../datadog/trace/util/AtomicsBenchmark.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java diff --git a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java new file mode 100644 index 00000000000..d2c950fb786 --- /dev/null +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -0,0 +1,161 @@ +package datadog.trace.util; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.Supplier; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +/** + * + * + * Instead of introucing an Atomic field into a class, a volatile field with an AtomicFieldUpdater is preferred when possible. + * + * + * While the performance of Atomic is on par with AtomicFieldUpdater (and sometimes slightly better) inside a frequently + * constructed object, the impact of the extra allocation on garbage collection is detrimental to the application as a whole. + * + * Java 17 - MacBook M1 - 8 threads + * Benchmark Mode Cnt Score Error Units + * AtomicsBenchmark.atomicFieldUpdater_construction thrpt 6 4442811623.924 ± 293568588.782 ops/s + * AtomicsBenchmark.atomicFieldUpdater_construction:gc.alloc.rate.norm thrpt 6 16.000 ± 0.001 B/op + + * AtomicsBenchmark.atomicFieldUpdater_get thrpt 6 1849238178.083 ± 29719143.488 ops/s + * AtomicsBenchmark.atomicFieldUpdater_get:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op + + * AtomicsBenchmark.atomicFieldUpdater_getVolatile thrpt 6 1814698391.230 ± 54378746.492 ops/s + * AtomicsBenchmark.atomicFieldUpdater_getVolatile:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op + + * AtomicsBenchmark.atomicFieldUpdater_incrementAndGet thrpt 6 15871753.651 ± 432451.444 ops/s + * AtomicsBenchmark.atomicFieldUpdater_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op + + * AtomicsBenchmark.atomic_construction thrpt 6 2532095943.171 ± 87644803.894 ops/s + * AtomicsBenchmark.atomic_construction:gc.alloc.rate.norm thrpt 6 32.000 ± 0.001 B/op + + * AtomicsBenchmark.atomic_incrementAndGet thrpt 6 16416635.546 ± 751137.083 ops/s + * AtomicsBenchmark.atomic_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op + + * AtomicsBenchmark.atomic_read thrpt 6 1943297600.062 ± 43802387.143 ops/s + * AtomicsBenchmark.atomic_read:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op + */ +@Fork(2) +@Warmup(iterations=2) +@Measurement(iterations=3) +@Threads(8) +public class AtomicsBenchmark { + static int SIZE = 32; + + static final class AtomicHolder { + final AtomicInteger atomic; + + AtomicHolder(int num) { + this.atomic = new AtomicInteger(num); + } + + int get() { + return this.atomic.get(); + } + + int incrementAndGet() { + return this.atomic.incrementAndGet(); + } + } + + static final class FieldHolder { + static final AtomicIntegerFieldUpdater AFU_FIELD = AtomicIntegerFieldUpdater.newUpdater(FieldHolder.class, "field"); + + volatile int field; + + FieldHolder(int num) { + this.field = num; + } + + int getVolatile() { + return this.field; + } + + int get() { + return AFU_FIELD.get(this); + } + + int incrementAndGet() { + return AFU_FIELD.incrementAndGet(this); + } + } + + static final AtomicHolder[] atomicHolders = init(() -> { + AtomicHolder[] holders = new AtomicHolder[SIZE]; + for ( int i = 0; i < holders.length; ++i ) { + holders[i] = new AtomicHolder(i * 2); + } + return holders; + }); + + static final FieldHolder[] fieldHolders = init(() -> { + FieldHolder[] holders = new FieldHolder[SIZE]; + for ( int i = 0; i < holders.length; ++i ) { + holders[i] = new FieldHolder(i * 2); + } + return holders; + }); + + static final T init(Supplier supplier) { + return supplier.get(); + } + + static int sharedLookupIndex = 0; + + static T next(T[] holders) { + int localIndex = ++sharedLookupIndex; + if ( localIndex >= holders.length ) { + sharedLookupIndex = localIndex = 0; + } + return holders[localIndex]; + } + + @Benchmark + public Object atomic_construction() { + return new AtomicHolder(0); + } + + @Benchmark + public int atomic_incrementAndGet() { + return next(atomicHolders).incrementAndGet(); + } + + @Benchmark + public Object atomic_read() { + return next(atomicHolders).get(); + } + + @Benchmark + public Object atomicFieldUpdater_construction() { + return new FieldHolder(0); + } + + @Benchmark + public Object atomicFieldUpdater_getVolatile() { + return next(fieldHolders).getVolatile(); + } + + @Benchmark + public Object atomicFieldUpdater_get() { + return next(fieldHolders).get(); + } + + @Benchmark + public int atomicFieldUpdater_incrementAndGet() { + return next(fieldHolders).incrementAndGet(); + } +} From fd5b9b0ec9431f5b104ef0dd4671cd51b0920c1e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 24 Mar 2026 08:59:37 -0400 Subject: [PATCH 2/2] spotless --- .../datadog/trace/util/AtomicsBenchmark.java | 174 +++++++++--------- 1 file changed, 89 insertions(+), 85 deletions(-) diff --git a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java index d2c950fb786..47a9119ae8c 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -3,7 +3,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Supplier; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; @@ -15,147 +14,152 @@ *
  • (RECOMMENDED) AtomicFieldUpdater - especially, when containing object is frequently constructed *
  • Atomic - usually, performs similarly to AtomicFieldUpdater - worse inside commonly constructed objects * - * + * * Instead of introucing an Atomic field into a class, a volatile field with an AtomicFieldUpdater is preferred when possible. *
      Types with AtomicFieldUpdaters are... *
    • int *
    • long *
    • reference (e.g. Object types) *
    - * - * While the performance of Atomic is on par with AtomicFieldUpdater (and sometimes slightly better) inside a frequently + * + * While the performance of Atomic is on par with AtomicFieldUpdater (and sometimes slightly better) inside a frequently * constructed object, the impact of the extra allocation on garbage collection is detrimental to the application as a whole. - * + * * Java 17 - MacBook M1 - 8 threads * Benchmark Mode Cnt Score Error Units * AtomicsBenchmark.atomicFieldUpdater_construction thrpt 6 4442811623.924 ± 293568588.782 ops/s * AtomicsBenchmark.atomicFieldUpdater_construction:gc.alloc.rate.norm thrpt 6 16.000 ± 0.001 B/op - + * * AtomicsBenchmark.atomicFieldUpdater_get thrpt 6 1849238178.083 ± 29719143.488 ops/s * AtomicsBenchmark.atomicFieldUpdater_get:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op - + * * AtomicsBenchmark.atomicFieldUpdater_getVolatile thrpt 6 1814698391.230 ± 54378746.492 ops/s * AtomicsBenchmark.atomicFieldUpdater_getVolatile:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op - + * * AtomicsBenchmark.atomicFieldUpdater_incrementAndGet thrpt 6 15871753.651 ± 432451.444 ops/s * AtomicsBenchmark.atomicFieldUpdater_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op - + * * AtomicsBenchmark.atomic_construction thrpt 6 2532095943.171 ± 87644803.894 ops/s * AtomicsBenchmark.atomic_construction:gc.alloc.rate.norm thrpt 6 32.000 ± 0.001 B/op - + * * AtomicsBenchmark.atomic_incrementAndGet thrpt 6 16416635.546 ± 751137.083 ops/s * AtomicsBenchmark.atomic_incrementAndGet:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁴ B/op - + * * AtomicsBenchmark.atomic_read thrpt 6 1943297600.062 ± 43802387.143 ops/s * AtomicsBenchmark.atomic_read:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op */ @Fork(2) -@Warmup(iterations=2) -@Measurement(iterations=3) +@Warmup(iterations = 2) +@Measurement(iterations = 3) @Threads(8) public class AtomicsBenchmark { static int SIZE = 32; - + static final class AtomicHolder { - final AtomicInteger atomic; - - AtomicHolder(int num) { - this.atomic = new AtomicInteger(num); - } - - int get() { - return this.atomic.get(); - } - - int incrementAndGet() { - return this.atomic.incrementAndGet(); - } + final AtomicInteger atomic; + + AtomicHolder(int num) { + this.atomic = new AtomicInteger(num); + } + + int get() { + return this.atomic.get(); + } + + int incrementAndGet() { + return this.atomic.incrementAndGet(); + } } - + static final class FieldHolder { - static final AtomicIntegerFieldUpdater AFU_FIELD = AtomicIntegerFieldUpdater.newUpdater(FieldHolder.class, "field"); - - volatile int field; - - FieldHolder(int num) { - this.field = num; - } - - int getVolatile() { - return this.field; - } - - int get() { - return AFU_FIELD.get(this); - } - - int incrementAndGet() { - return AFU_FIELD.incrementAndGet(this); - } - } - - static final AtomicHolder[] atomicHolders = init(() -> { - AtomicHolder[] holders = new AtomicHolder[SIZE]; - for ( int i = 0; i < holders.length; ++i ) { - holders[i] = new AtomicHolder(i * 2); + static final AtomicIntegerFieldUpdater AFU_FIELD = + AtomicIntegerFieldUpdater.newUpdater(FieldHolder.class, "field"); + + volatile int field; + + FieldHolder(int num) { + this.field = num; } - return holders; - }); - - static final FieldHolder[] fieldHolders = init(() -> { - FieldHolder[] holders = new FieldHolder[SIZE]; - for ( int i = 0; i < holders.length; ++i ) { - holders[i] = new FieldHolder(i * 2); + + int getVolatile() { + return this.field; } - return holders; - }); - + + int get() { + return AFU_FIELD.get(this); + } + + int incrementAndGet() { + return AFU_FIELD.incrementAndGet(this); + } + } + + static final AtomicHolder[] atomicHolders = + init( + () -> { + AtomicHolder[] holders = new AtomicHolder[SIZE]; + for (int i = 0; i < holders.length; ++i) { + holders[i] = new AtomicHolder(i * 2); + } + return holders; + }); + + static final FieldHolder[] fieldHolders = + init( + () -> { + FieldHolder[] holders = new FieldHolder[SIZE]; + for (int i = 0; i < holders.length; ++i) { + holders[i] = new FieldHolder(i * 2); + } + return holders; + }); + static final T init(Supplier supplier) { - return supplier.get(); + return supplier.get(); } - + static int sharedLookupIndex = 0; - + static T next(T[] holders) { - int localIndex = ++sharedLookupIndex; - if ( localIndex >= holders.length ) { - sharedLookupIndex = localIndex = 0; - } - return holders[localIndex]; + int localIndex = ++sharedLookupIndex; + if (localIndex >= holders.length) { + sharedLookupIndex = localIndex = 0; + } + return holders[localIndex]; } - + @Benchmark public Object atomic_construction() { - return new AtomicHolder(0); + return new AtomicHolder(0); } - + @Benchmark public int atomic_incrementAndGet() { - return next(atomicHolders).incrementAndGet(); + return next(atomicHolders).incrementAndGet(); } - + @Benchmark public Object atomic_read() { - return next(atomicHolders).get(); + return next(atomicHolders).get(); } - + @Benchmark public Object atomicFieldUpdater_construction() { - return new FieldHolder(0); + return new FieldHolder(0); } - + @Benchmark public Object atomicFieldUpdater_getVolatile() { - return next(fieldHolders).getVolatile(); + return next(fieldHolders).getVolatile(); } - + @Benchmark public Object atomicFieldUpdater_get() { - return next(fieldHolders).get(); + return next(fieldHolders).get(); } - + @Benchmark public int atomicFieldUpdater_incrementAndGet() { - return next(fieldHolders).incrementAndGet(); + return next(fieldHolders).incrementAndGet(); } }