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/9] 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/9] 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(); } } From c1b14407fb6c5bb6a12cd6bbd3beb1749392a058 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 17:05:16 -0400 Subject: [PATCH 3/9] Update internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java Co-authored-by: Sarah Chen --- .../src/jmh/java/datadog/trace/util/AtomicsBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 47a9119ae8c..5a48ce8a988 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -15,7 +15,7 @@ *
  • 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. + * Instead of introducing an Atomic field into a class, a volatile field with an AtomicFieldUpdater is preferred when possible. *
      Types with AtomicFieldUpdaters are... *
    • int *
    • long From b9cd707aa454dcba758841f77415ca2f50a1ee1b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 16:31:53 -0400 Subject: [PATCH 4/9] Fix data race in AtomicsBenchmark scaffolding index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sharedLookupIndex was a plain static int incremented by all 8 JMH threads without synchronization — a data race that could cause contention measurements to reflect arbitrary holder access patterns. Move index to @State(Scope.Thread) so each thread has its own cursor. Co-Authored-By: Claude Sonnet 4.6 --- .../datadog/trace/util/AtomicsBenchmark.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 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 5a48ce8a988..16a048ff7fe 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -6,6 +6,8 @@ import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; @@ -118,14 +120,14 @@ static final T init(Supplier supplier) { return supplier.get(); } - static int sharedLookupIndex = 0; + @State(Scope.Thread) + public static class BenchmarkState { + int index = 0; - static T next(T[] holders) { - int localIndex = ++sharedLookupIndex; - if (localIndex >= holders.length) { - sharedLookupIndex = localIndex = 0; + T next(T[] holders) { + if (++index >= holders.length) index = 0; + return holders[index]; } - return holders[localIndex]; } @Benchmark @@ -134,13 +136,13 @@ public Object atomic_construction() { } @Benchmark - public int atomic_incrementAndGet() { - return next(atomicHolders).incrementAndGet(); + public int atomic_incrementAndGet(BenchmarkState state) { + return state.next(atomicHolders).incrementAndGet(); } @Benchmark - public Object atomic_read() { - return next(atomicHolders).get(); + public Object atomic_read(BenchmarkState state) { + return state.next(atomicHolders).get(); } @Benchmark @@ -149,17 +151,17 @@ public Object atomicFieldUpdater_construction() { } @Benchmark - public Object atomicFieldUpdater_getVolatile() { - return next(fieldHolders).getVolatile(); + public Object atomicFieldUpdater_getVolatile(BenchmarkState state) { + return state.next(fieldHolders).getVolatile(); } @Benchmark - public Object atomicFieldUpdater_get() { - return next(fieldHolders).get(); + public Object atomicFieldUpdater_get(BenchmarkState state) { + return state.next(fieldHolders).get(); } @Benchmark - public int atomicFieldUpdater_incrementAndGet() { - return next(fieldHolders).incrementAndGet(); + public int atomicFieldUpdater_incrementAndGet(BenchmarkState state) { + return state.next(fieldHolders).incrementAndGet(); } } From 6932a0a460b9a98a58dc82a2275cd0132958f684 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:37:33 -0400 Subject: [PATCH 5/9] Wire -Pjmh.includes and -PtestJvm into internal-api JMH config Without this, -Pjmh.includes is silently ignored by the me.champeau.jmh plugin, requiring a full fat-jar build to run a single benchmark. -PtestJvm was also ignored for JMH execution, defaulting to the Gradle daemon JVM regardless of the requested version. Co-Authored-By: Claude Sonnet 4.6 --- internal-api/build.gradle.kts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal-api/build.gradle.kts b/internal-api/build.gradle.kts index b78e5b38b14..9ac19c8fc0d 100644 --- a/internal-api/build.gradle.kts +++ b/internal-api/build.gradle.kts @@ -1,3 +1,4 @@ +import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis import groovy.lang.Closure @@ -281,4 +282,13 @@ dependencies { jmh { jmhVersion = libs.versions.jmh.get() duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE + + if (project.hasProperty("jmh.includes")) { + includes.add(project.property("jmh.includes") as String) + } + + if (project.hasProperty("testJvm")) { + val testJvmSpec = TestJvmSpec(project) + jvm.set(testJvmSpec.javaTestLauncher.map { it.executablePath.asFile.absolutePath }) + } } From c77876061d6b9016e59cdc7673896b41c3403143 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:54:56 -0400 Subject: [PATCH 6/9] Update AtomicsBenchmark results with corrected Java 17 numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-run after fixing the shared-index data race. The incrementAndGet results increased dramatically (~16M → ~91-121M ops/s) because the prior run was measuring contention on the shared index rather than the atomics themselves. Notable: atomic_incrementAndGet (121M) now outperforms atomicFieldUpdater_incrementAndGet (91M) for the increment op; the AFU advantage is in construction (2215M vs 1872M, saving 16 B/op). Co-Authored-By: Claude Sonnet 4.6 --- .../java/datadog/trace/util/AtomicsBenchmark.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 16a048ff7fe..84d02a161e0 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -29,25 +29,25 @@ * * 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 thrpt 6 2215272588.708 ± 88556141.052 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 thrpt 6 2174739788.040 ± 56980971.014 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 thrpt 6 2157331061.707 ± 136900932.336 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 thrpt 6 90785783.320 ± 7650837.727 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 thrpt 6 1872153219.594 ± 83252749.463 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 thrpt 6 120835704.294 ± 23025991.947 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 thrpt 6 1968266961.596 ± 57765039.412 ops/s * AtomicsBenchmark.atomic_read:gc.alloc.rate.norm thrpt 6 ≈ 10⁻⁶ B/op */ @Fork(2) From 7f56a97beed850e3fcde9f2a1a0286bcd715d958 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:57:21 -0400 Subject: [PATCH 7/9] Clarify AtomicsBenchmark recommendation: tradeoff depends on access pattern AFU wins on construction (16 B vs 32 B/op); AtomicInteger wins on incrementAndGet (~33% faster). Updated Javadoc to present both sides rather than a blanket recommendation. Co-Authored-By: Claude Sonnet 4.6 --- .../datadog/trace/util/AtomicsBenchmark.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 84d02a161e0..6f955e40674 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -12,20 +12,20 @@ import org.openjdk.jmh.annotations.Warmup; /** - *
        - *
      • (RECOMMENDED) AtomicFieldUpdater - especially, when containing object is frequently constructed - *
      • Atomic - usually, performs similarly to AtomicFieldUpdater - worse inside commonly constructed objects - *
      + * The choice between {@link AtomicInteger} and {@link AtomicIntegerFieldUpdater} depends on the + * access pattern: * - * Instead of introducing 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) + *
          + *
        • Frequently constructed objects: prefer AtomicFieldUpdater. It saves 16 B/op at + * construction (one fewer object) — the GC impact of that allocation compounds over the + * lifetime of the application. + *
        • Long-lived objects with heavy incrementAndGet use: AtomicInteger is ~33% faster for + * incrementAndGet (121M vs 91M ops/s). AtomicIntegerFieldUpdater carries overhead from its + * reflective field-access path that C2 cannot intrinsify as cleanly. + *
        • Read-heavy paths: essentially a wash (both ~2 B ops/s). *
        * - * 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. + * AtomicFieldUpdater supports {@code int}, {@code long}, and reference types. * * Java 17 - MacBook M1 - 8 threads * Benchmark Mode Cnt Score Error Units From bb784c0bb1023446f40f90c2e7bc682b8143a8b8 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:59:59 -0400 Subject: [PATCH 8/9] Add VarHandle note to AtomicsBenchmark Javadoc VarHandle (Java 9+) is the modern replacement for AtomicIntegerFieldUpdater and would likely close the incrementAndGet performance gap. Not usable here because internal-api targets Java 8. Co-Authored-By: Claude Sonnet 4.6 --- .../src/jmh/java/datadog/trace/util/AtomicsBenchmark.java | 5 +++++ 1 file changed, 5 insertions(+) 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 6f955e40674..15118228b29 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -27,6 +27,11 @@ * * AtomicFieldUpdater supports {@code int}, {@code long}, and reference types. * + *

        Future: {@code VarHandle} (Java 9+) is the modern replacement for + * AtomicIntegerFieldUpdater. It avoids the reflective field-access overhead, which should close + * the incrementAndGet gap with AtomicInteger while retaining the construction allocation advantage. + * Not available here because internal-api targets Java 8. + * * Java 17 - MacBook M1 - 8 threads * Benchmark Mode Cnt Score Error Units * AtomicsBenchmark.atomicFieldUpdater_construction thrpt 6 2215272588.708 ± 88556141.052 ops/s From a3906f85a58d24ce92a4af97d2e1bafbe05877c1 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 23 Jun 2026 14:17:08 -0400 Subject: [PATCH 9/9] Drop JMH gradle wiring from atomics PR; fix results header machine name The internal-api/build.gradle.kts -Pjmh.includes / -PtestJvm wiring belongs in its dedicated PR (#11703), not here. Revert it so this PR is just the benchmark. Also correct the results header to "MacBook M1 Pro Max". Co-Authored-By: Claude Opus 4.8 (1M context) --- internal-api/build.gradle.kts | 10 ---------- .../jmh/java/datadog/trace/util/AtomicsBenchmark.java | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/internal-api/build.gradle.kts b/internal-api/build.gradle.kts index c6e03a03883..305dead4459 100644 --- a/internal-api/build.gradle.kts +++ b/internal-api/build.gradle.kts @@ -1,4 +1,3 @@ -import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis import groovy.lang.Closure @@ -275,13 +274,4 @@ dependencies { jmh { jmhVersion = libs.versions.jmh.get() duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE - - if (project.hasProperty("jmh.includes")) { - includes.add(project.property("jmh.includes") as String) - } - - if (project.hasProperty("testJvm")) { - val testJvmSpec = TestJvmSpec(project) - jvm.set(testJvmSpec.javaTestLauncher.map { it.executablePath.asFile.absolutePath }) - } } 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 15118228b29..b1a7f0d26c1 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/AtomicsBenchmark.java @@ -32,7 +32,7 @@ * the incrementAndGet gap with AtomicInteger while retaining the construction allocation advantage. * Not available here because internal-api targets Java 8. * - * Java 17 - MacBook M1 - 8 threads + * Java 17 - MacBook M1 Pro Max - 8 threads * Benchmark Mode Cnt Score Error Units * AtomicsBenchmark.atomicFieldUpdater_construction thrpt 6 2215272588.708 ± 88556141.052 ops/s * AtomicsBenchmark.atomicFieldUpdater_construction:gc.alloc.rate.norm thrpt 6 16.000 ± 0.001 B/op