From d2a22954cb6584110cfcb0630cedb1f8c3bbc7ac Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 18 Mar 2026 09:12:14 -0400 Subject: [PATCH 1/8] List iteration benchmark --- .../trace/util/ListIterationBenchmark.java | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java new file mode 100644 index 00000000000..c32b924864a --- /dev/null +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -0,0 +1,412 @@ +package datadog.trace.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.CompilerControl.Mode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmark comparing difference ways to iterate list of different types and sizes -- both with + * simple loop bodies (inline case) and complicated loop bodies (dont inline case). + * + * + * Java 17 - MacBook M1 - 8 threads * Benchmark (listSpec) Mode Cnt Score Error Units * ListIterationBenchmark.cstyleFor_inline COLLECTIONS_EMPTY_LIST thrpt 3 9066154714.207 ± 3993855570.335 ops/s * ListIterationBenchmark.cstyleFor:gc.alloc.rate.norm COLLECTIONS_EMPTY_LIST thrpt 3 ≈ 10⁻⁷ B/op @@ -335,75 +337,74 @@ public enum ListSpec { @Param ListSpec listSpec; - @Benchmark - public void forEach_inline() { - this.listSpec.list.forEach(Element::manipulate_inline); - } - - @Benchmark - public void forEach_dont_inline() { - this.listSpec.list.forEach(Element::manipulate_dont_inline); - } - - @Benchmark - public void enhancedFor_inline() { - // Enhanced for-loop is just syntax sugar for an Iterator - for ( Element e : this.listSpec.list ) { - e.manipulate_inline(); - } - } - - @Benchmark - public void enhancedFor_dont_inline() { - // Enhanced for-loop is just syntax sugar for an Iterator - for ( Element e : this.listSpec.list ) { - e.manipulate_dont_inline(); - } - } - - @Benchmark - public void iterator_inline() { - for ( Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { - iter.next().manipulate_inline(); - } - } - - @Benchmark - public void iterator_dont_inline() { - for ( Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { - iter.next().manipulate_dont_inline(); - } + @Benchmark + public void forEach_inline() { + this.listSpec.list.forEach(Element::manipulate_inline); + } + + @Benchmark + public void forEach_dont_inline() { + this.listSpec.list.forEach(Element::manipulate_dont_inline); + } + + @Benchmark + public void enhancedFor_inline() { + // Enhanced for-loop is just syntax sugar for an Iterator + for (Element e : this.listSpec.list) { + e.manipulate_inline(); } - - - @Benchmark - public void cstyleFor_inline() { - for ( int i = 0; i < this.listSpec.list.size(); ++i ) { - this.listSpec.list.get(i).manipulate_inline(); - } + } + + @Benchmark + public void enhancedFor_dont_inline() { + // Enhanced for-loop is just syntax sugar for an Iterator + for (Element e : this.listSpec.list) { + e.manipulate_dont_inline(); } - - @Benchmark - public void cstyleFor_dont_inline() { - for ( int i = 0; i < this.listSpec.list.size(); ++i ) { - this.listSpec.list.get(i).manipulate_dont_inline(); - } + } + + @Benchmark + public void iterator_inline() { + for (Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { + iter.next().manipulate_inline(); } - - @Benchmark - public void streams_inline() { - this.listSpec.list.stream().forEach(Element::manipulate_inline); + } + + @Benchmark + public void iterator_dont_inline() { + for (Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { + iter.next().manipulate_dont_inline(); } - - @Benchmark - public void streams_dont_inline() { - this.listSpec.list.stream().forEach(Element::manipulate_dont_inline); + } + + @Benchmark + public void cstyleFor_inline() { + for (int i = 0; i < this.listSpec.list.size(); ++i) { + this.listSpec.list.get(i).manipulate_inline(); } - - @Benchmark - public void parallelStreams_inline() { - listSpec.list.parallelStream().forEach(Element::manipulate_dont_inline); + } + + @Benchmark + public void cstyleFor_dont_inline() { + for (int i = 0; i < this.listSpec.list.size(); ++i) { + this.listSpec.list.get(i).manipulate_dont_inline(); } + } + + @Benchmark + public void streams_inline() { + this.listSpec.list.stream().forEach(Element::manipulate_inline); + } + + @Benchmark + public void streams_dont_inline() { + this.listSpec.list.stream().forEach(Element::manipulate_dont_inline); + } + + @Benchmark + public void parallelStreams_inline() { + listSpec.list.parallelStream().forEach(Element::manipulate_dont_inline); + } @Benchmark public void parallelStreams_dont_inline() { From 27a33f03d98fdfc4616fa3e9e49ea09878a95c18 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 8 Apr 2026 12:47:38 -0400 Subject: [PATCH 4/8] spotless --- .../src/jmh/java/datadog/trace/util/ListIterationBenchmark.java | 1 + 1 file changed, 1 insertion(+) diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java index 2b7130abfa9..99ff929f9ef 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -31,6 +31,7 @@ * working with sets (uncommon in the java agent) *
  • * + * * Java 17 - MacBook M1 - 8 threads * Benchmark (listSpec) Mode Cnt Score Error Units * ListIterationBenchmark.cstyleFor_inline COLLECTIONS_EMPTY_LIST thrpt 3 9066154714.207 ± 3993855570.335 ops/s From 8056b562d3a25aa004826b85f9a60fb8e17bc045 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:29:55 -0400 Subject: [PATCH 5/8] Update internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java Co-authored-by: Sarah Chen --- .../src/jmh/java/datadog/trace/util/ListIterationBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java index 99ff929f9ef..0bdf3242e1d 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -404,7 +404,7 @@ public void streams_dont_inline() { @Benchmark public void parallelStreams_inline() { - listSpec.list.parallelStream().forEach(Element::manipulate_dont_inline); + listSpec.list.parallelStream().forEach(Element::manipulate_inline); } @Benchmark From b816fdeed7ff8d2d4392b54bba6443aadb497c22 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:30:08 -0400 Subject: [PATCH 6/8] Update internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java Co-authored-by: Sarah Chen --- .../src/jmh/java/datadog/trace/util/ListIterationBenchmark.java | 1 + 1 file changed, 1 insertion(+) diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java index 0bdf3242e1d..62bab289740 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.CompilerControl; From 600cc9bbc171448967425401792f3cfa15c3f765 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 23 Jun 2026 13:36:25 -0400 Subject: [PATCH 7/8] Update internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java Co-authored-by: Sarah Chen --- .../src/jmh/java/datadog/trace/util/ListIterationBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java index 62bab289740..b7c527e99b3 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -16,7 +16,7 @@ import org.openjdk.jmh.annotations.Warmup; /** - * Benchmark comparing difference ways to iterate list of different types and sizes -- both with + * Benchmark comparing different ways to iterate list of different types and sizes -- both with * simple loop bodies (inline case) and complicated loop bodies (dont inline case). * *
      From 26c2c0bcd70c40d8a9776221aa793b28a377ad97 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 23 Jun 2026 13:47:42 -0400 Subject: [PATCH 8/8] Isolate per-thread collections in ListIterationBenchmark Build each thread's list (and its Elements) in a Scope.Thread @Setup so the manipulate_* mutations stay thread-local. Previously the lists lived in enum constants shared across all 8 threads, so the benchmark measured cross-thread contention on Element.num rather than iteration cost. Also bump to @Fork(2) and fix a Javadoc typo. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../trace/util/ListIterationBenchmark.java | 74 ++++++++++++------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java index b7c527e99b3..e9bc52e12a5 100644 --- a/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java +++ b/internal-api/src/jmh/java/datadog/trace/util/ListIterationBenchmark.java @@ -4,13 +4,16 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Supplier; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.CompilerControl; import org.openjdk.jmh.annotations.CompilerControl.Mode; import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; @@ -293,11 +296,11 @@ * ListIterationBenchmark.streams_dont_inline:gc.alloc.rate.norm ARRAY_LIST_100 thrpt 3 88.000 ± 0.001 B/op * */ -@Fork(1) +@Fork(2) @Warmup(iterations = 2) @Measurement(iterations = 3) @Threads(8) -@State(Scope.Benchmark) +@State(Scope.Thread) public class ListIterationBenchmark { public static final class Element { int num = 0; @@ -321,38 +324,57 @@ static ArrayList newArrayList(int size) { return newList; } + /** + * Describes the list under test as a factory rather than a prebuilt instance. Each benchmark + * thread builds its own list (with its own {@link Element}s) in {@link #setUp()}, so the {@code + * manipulate_*} mutations stay thread-local — otherwise, with {@code @Threads(8)} sharing one + * list held in an enum constant, the benchmark would measure cross-thread contention on {@code + * Element.num} rather than iteration cost. + */ public enum ListSpec { - COLLECTIONS_EMPTY_LIST(Collections.emptyList()), - EMPTY_ARRAY_LIST(new ArrayList<>()), - SINGLETON_LIST(Collections.singletonList(new Element())), - ARRAY_LIST_1(newArrayList(1)), - ARRAY_LIST_5(newArrayList(5)), - ARRAY_LIST_10(newArrayList(10)), - ARRAY_LIST_100(newArrayList(100)); + COLLECTIONS_EMPTY_LIST(Collections::emptyList), + EMPTY_ARRAY_LIST(ArrayList::new), + SINGLETON_LIST(() -> Collections.singletonList(new Element())), + ARRAY_LIST_1(() -> newArrayList(1)), + ARRAY_LIST_5(() -> newArrayList(5)), + ARRAY_LIST_10(() -> newArrayList(10)), + ARRAY_LIST_100(() -> newArrayList(100)); - final List list; + private final Supplier> factory; - ListSpec(List list) { - this.list = list; + ListSpec(Supplier> factory) { + this.factory = factory; + } + + List build() { + return factory.get(); } } @Param ListSpec listSpec; + List list; + + @Setup(Level.Trial) + public void setUp() { + // Built per thread (the class is @State(Scope.Thread)) so each thread owns its own Elements. + this.list = this.listSpec.build(); + } + @Benchmark public void forEach_inline() { - this.listSpec.list.forEach(Element::manipulate_inline); + this.list.forEach(Element::manipulate_inline); } @Benchmark public void forEach_dont_inline() { - this.listSpec.list.forEach(Element::manipulate_dont_inline); + this.list.forEach(Element::manipulate_dont_inline); } @Benchmark public void enhancedFor_inline() { // Enhanced for-loop is just syntax sugar for an Iterator - for (Element e : this.listSpec.list) { + for (Element e : this.list) { e.manipulate_inline(); } } @@ -360,56 +382,56 @@ public void enhancedFor_inline() { @Benchmark public void enhancedFor_dont_inline() { // Enhanced for-loop is just syntax sugar for an Iterator - for (Element e : this.listSpec.list) { + for (Element e : this.list) { e.manipulate_dont_inline(); } } @Benchmark public void iterator_inline() { - for (Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { + for (Iterator iter = this.list.iterator(); iter.hasNext(); ) { iter.next().manipulate_inline(); } } @Benchmark public void iterator_dont_inline() { - for (Iterator iter = this.listSpec.list.iterator(); iter.hasNext(); ) { + for (Iterator iter = this.list.iterator(); iter.hasNext(); ) { iter.next().manipulate_dont_inline(); } } @Benchmark public void cstyleFor_inline() { - for (int i = 0; i < this.listSpec.list.size(); ++i) { - this.listSpec.list.get(i).manipulate_inline(); + for (int i = 0; i < this.list.size(); ++i) { + this.list.get(i).manipulate_inline(); } } @Benchmark public void cstyleFor_dont_inline() { - for (int i = 0; i < this.listSpec.list.size(); ++i) { - this.listSpec.list.get(i).manipulate_dont_inline(); + for (int i = 0; i < this.list.size(); ++i) { + this.list.get(i).manipulate_dont_inline(); } } @Benchmark public void streams_inline() { - this.listSpec.list.stream().forEach(Element::manipulate_inline); + this.list.stream().forEach(Element::manipulate_inline); } @Benchmark public void streams_dont_inline() { - this.listSpec.list.stream().forEach(Element::manipulate_dont_inline); + this.list.stream().forEach(Element::manipulate_dont_inline); } @Benchmark public void parallelStreams_inline() { - listSpec.list.parallelStream().forEach(Element::manipulate_inline); + this.list.parallelStream().forEach(Element::manipulate_inline); } @Benchmark public void parallelStreams_dont_inline() { - listSpec.list.parallelStream().forEach(Element::manipulate_dont_inline); + this.list.parallelStream().forEach(Element::manipulate_dont_inline); } }