Skip to content

Add BenchmarkUtils.warmUp to restore cross-arm branch profiles in benchmarks (perf toolkit - benchmarking tools) - #12651

Draft
dougqh wants to merge 58 commits into
masterfrom
dougqh/benchmarkutils-warmup-arms
Draft

dougqh wants to merge 58 commits into
masterfrom
dougqh/benchmarkutils-warmup-arms

Conversation

@dougqh

@dougqh dougqh commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What Does This Do

Adds BenchmarkUtils.warmUp, a JMH setup helper that drives every path through a benchmark class (each @Benchmark method, plus each interesting outcome of a multi-outcome method) through many shuffled-order passes before measurement starts.

  • Explicit form: warmUp(bench, [state...], bh, bench(...)...), with arities 0–2 for @State parameters. bench(...) is overloaded on a small set of java.util.function shapes (Function/Consumer, the Bi* versions, plus the existing datadog.trace.api.function.TriFunction/TriConsumer), so a lambda or method reference to an existing benchmark method fits directly. Primitive results get boxed on the way into the Blackhole. That cost lands only during warmup, never on the measured path.
  • Reflective form: warmUp(Class<?>, bh) finds every @Benchmark method on the class, builds the benchmark class and its @State objects through their no-arg constructors, runs their no-arg @Setup methods, and drives one call per method. It needs no setup work, but drives only one outcome per method.
  • The shuffle order changes on every pass. It's seeded, so runs are reproducible, and it reuses the same array, so passes don't allocate. That way, wherever C2 starts compiling during warmup, it sees the class's full mix of paths, not whichever one happens to come first.
  • Adds WarmUpEscapeAnalysisDemoBenchmark, which shows the effect (see Additional Notes).

Motivation

JMH forks a fresh JVM for each @Benchmark method, so each fork's C2 compiles code that has only ever seen one benchmark's paths. Production is the opposite: one JVM runs everything. warmUpHashDispatch (#12294/#12298) already handles the receiver-type and class-hierarchy side of this. The remaining gap is one-sided branch profiles.

That gap can hide real allocations. In ThreadSafeMapD2Benchmark, @Setup pre-installs every key, so the miss branch of getOrCreate never runs. C2 drops that branch entirely (an unstable_if trap), the composite key no longer escapes, and escape analysis removes its allocation. The benchmark reports ≈0 B/op for a lookup that allocates a key on every call in a real cache, since a real cache's early misses leave that branch two-sided.

Additional Notes

Evidence. WarmUpEscapeAnalysisDemoBenchmark runs the same getOrCreate code in two arms, always hitting in the measured lookup. The only difference is whether warmUp first primes that call site once with a genuine miss, using scratch data:

pollute ops/µs B/op gc.count
false 1610.2 ± 82.4 ≈0 ≈0
true 1154.7 ± 66.8 24.0 692

Zulu 17.0.7, Apple M1, 2 forks, 8 threads, -prof gc. Priming brings back the composite key's real 24 B/op and its ~28% throughput cost, without changing the measured method or the measured map. The demo clears its scratch map after warmUp, so the extra keys inserted on miss passes aren't kept live during measurement.

Known limits.

  • The explicit form can only check that at least as many paths were registered as there are matching @Benchmark methods (including those inherited, since JMH runs @Setup on a generated subclass). It can't verify that every outcome of a multi-outcome method was driven, and registering only the hit path of getOrCreate is worse than not calling warmUp at all.
  • The reflective form runs only no-arg @Setup methods (on the benchmark class and its state objects), leaves @Param fields at their Java defaults, and drives only the outcome that its reflectively built state happens to produce.

Not yet adopted. No production benchmark uses warmUp yet. Adopting it in ThreadSafeMapD2Benchmark means writing a miss path for absent keys, and every benchmark that adopts it has to be re-measured. That happens in a follow-up.

Stacked on #12298.

Contributor Checklist

Jira ticket: APMLP-1870

🤖 Generated with Claude Code

dougqh and others added 30 commits September 22, 2026 14:33
Mirrors StringIndex#contains for the static-arrays path, so callers
of the embedded/parallel-array form don't need to spell out
indexOf(...) >= 0 themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…hmark

Reusing the same interned/cached-hash key instances for both building
a structure and measuring hit lookups against it understates real hit
cost -- String#equals's == fast path and String#hashCode's cached
result pay nothing extra for a key already touched during setUp. The
new hitFresh scenario looks up separate, never-touched String
instances instead, alongside the existing hit (interned literal) and
miss (already representative) scenarios.

Also pulls in BenchmarkUtils#polluteHashDispatch so the shared
hashCode/equals call sites are already megamorphic before the
StringSet arms measure their own lookups, matching production where
those call sites are hit by every hash-based structure in the JVM.

Replaced the javadoc's stale, partial-run tables/findings with a full
hit/hitFresh/miss re-run across all six structures together (Fork(5),
Threads(8)) -- confirms hitFresh is the slowest of the three scenarios
for every hash-based structure, not merely slower than hit as
previously guessed from partial data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Recompute the slot index each iteration as (h + probes) & mask instead
of carrying a separately-incremented cursor, so the loop has one
canonical induction variable for the JIT to reason about.
…ains-only helpers

populateTypeProfile() drives only contains(), so it works against both
mutable and immutable collections (including the collection instance
under test itself); populateTypeProfileMutable() keeps the old
add()+contains() behavior for callers that need add() dispatch
polluted too.
The instance wrapper reliably beats HashSet on hit (its actual design
case) but not on miss/hitFresh, where both are bimodal across forks
and the mean already sits at or below HashSet's steady figure. Point
miss/fresh-key-heavy callers at EmbeddingSupport directly instead of
assuming the wrapper is strictly better than a plain Set.
Object.hashCode()/equals() are JVM-wide shared call sites hit by every
hash-based structure in the process; leaving them monomorphic for a
single-key-type run understates real dispatch cost, same reasoning as
ImmutableSetBenchmark. Rerun and update the javadoc results table with
pollution in effect -- StringIndex's get win over HashMap/TagMap/MapN
holds up unconditionally here, unlike the access-pattern-dependent Set
case.
Apply the reviewer-suggested rewordings for BenchmarkUtils, ImmutableSetBenchmark,
ImmutableMapBenchmark, and StringIndex: trim narration and restate a couple of
claims (fresh-string hash caching, contains() contract) more precisely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
polluteHashDispatch() only warmed the shared HashSet/HashMap call site;
ConcurrentHashMap has its own, unrelated hashCode()/equals() dispatch
sites and needs a dedicated scratch instance. Also add
populateTypeProfileMap/populateTypeProfileMutableMap as the Map
counterparts to the existing Collection-based helpers, for benchmarks
that need finer-grained control than polluteHashDispatch() gives.
…nchmarks

Applies the type-profile-pollution fix already used in
ImmutableSetBenchmark/ImmutableMapBenchmark to the rest of the
HashMap/HashSet/ConcurrentHashMap-comparing benchmarks in this module:
SingleThreadedMapBenchmark, SingleThreadedSetBenchmark,
ThreadSafeMapBenchmark, HashtableD1Benchmark, HashtableD2Benchmark,
CaseInsensitiveMapBenchmark, and TagMapAccessBenchmark. Without this,
each file's isolated single-key-type usage left the JDK collections'
shared internal hashCode()/equals() dispatch call sites artificially
monomorphic, understating their real per-call cost.

FlatHashtableIteratorBenchmark is intentionally untouched -- it only
exercises the project's own FlatHashtable/HashStrategy, not
java.util.HashMap/HashSet/ConcurrentHashMap.
D1.Entry.hash/matches are call sites private to Hashtable.java, distinct
from java.util.HashMap/HashSet's own dispatch sites, so pollution
literally cannot reach them (not just unlikely to). Since JDK and
machine were held constant in this rerun, attribute the observed drop
to same-session run-to-run noise, not a JDK effect (that explanation
applies elsewhere, where the JDK actually changed).
Same fix as HashtableD1Benchmark: D2.Entry.hash/matches are call sites
private to Hashtable.java, structurally distinct from java.util.HashMap
dispatch, so pollution cannot reach them regardless of key-type
overlap; the observed drop with JDK/machine held constant is
same-session noise, not a JDK effect.
The original table is Zulu 21; this rerun is JDK 8, whose C2 backend
for Apple Silicon is far less mature than JDK 17+'s. That JDK gap
explains a broad-based slowdown across every entry on its own,
including flatHashtable/treeMap which don't touch java.util.HashMap
dispatch — a more precise explanation than generic "machine variance."
Same fix as CaseInsensitiveMapBenchmark: the original table is Java 17,
this rerun is JDK 8, and JDK 8's weaker Apple Silicon C2 codegen
explains the across-the-board drop on its own. Distinguish this from
HashtableD1Benchmark/HashtableD2Benchmark, where the JDK was held
constant and the drop is same-session noise instead.
Controlled rerun (same machine, JDK swapped to Zulu 17.0.7) shows
update_hashtable still wins decisively (~4.2x, down from ~14x on JDK 8)
and iterate_hashtable flips from a wash to a ~4x win, while add_hashMap
edges ahead slightly. Java 17's much better allocator/GC narrows but
doesn't erase Hashtable's win on the allocation-heavy paths -- net
takeaway: Hashtable is a strong HashMap substitute for simple
counter/tally cases with a primitive value.

Absolute numbers aren't comparable to the JDK 8 table: JMH auto-selects
the cheap "compiler" Blackhole mode on 17 but not on 8, so within-run
ratios are the only trustworthy comparison here.
Same controlled rerun as HashtableD1Benchmark. update_hashtable wins
~11.6x (down from ~26x on JDK 8); unlike JDK 8, Hashtable now also
wins clearly on add (~1.8x) and iterate (~3.4x, up from a wash) --
Java 17's allocator/GC narrows the update margin but doesn't flip any
operation in HashMap's favor for D2. Same Blackhole-mode caveat on
absolute numbers as D1; within-run ratios are what's trustworthy here.
BenchmarkUtils's contains()/get() probes reused the exact same key
instance just added/put, so HashMap/ConcurrentHashMap's internal
identity fast path (key == storedKey) short-circuited before equals()
was ever invoked -- the helper was polluting hashCode() dispatch but
not equals() dispatch, undermining its own purpose (Codex P1, Autotest
P2). Added distinctEqualCopy() to probe with an equal-but-not-identical
instance instead.

Also fixes: mislabeled "M ops/us" throughput unit in the Java 17 tables
(HashtableD1/D2Benchmark, should read ops/us like the JDK 8 tables
above them), a Javadoc comment in TagMapAccessBenchmark left describing
ReadMap after a @setup method was inserted above it, and an invalid
cross-JDK comparison in SingleThreadedSetBenchmark's commentary that
attributed unchanged numbers to "pollution had no effect" without
acknowledging the JDK 8-vs-17 confound.

Note: the BenchmarkUtils fix changes what polluteHashDispatch() actually
exercises, so the "Java 17 rerun" numbers recorded in ThreadSafeMapD1/D2/
CounterBenchmark's javadoc (added in an earlier commit on this branch)
are still pending a fresh rerun against this corrected helper.
…Map benchmarks

Reruns ThreadSafeMapD1/D2/CounterBenchmark with BenchmarkUtils.polluteHashDispatch()
now fixed (commit 3d9b93a) to avoid the identity fast-path bypass. Documents the
fresh JDK 8 numbers alongside the pre-existing Java 17 tables, with the same
cross-JDK-comparison caveat used elsewhere in this PR since these reruns aren't on
the same JDK as the original tables.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reruns ThreadSafeMapD1/D2/CounterBenchmark on the same Zulu 17 JVM as the
original baseline table, superseding the earlier JDK 8 rerun (56a31d9)
whose numbers were confounded by a JDK 8 vs 17 optimizer difference on
arm64. The clean delta surfaces a real finding the confounded run
couldn't: synchronized HashMap throughput collapses by ~67-73% once hash
dispatch is polluted, far more than the lock contention these benchmarks
were designed to isolate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
polluteHashDispatch() only made hash-based (equals()/hashCode())
dispatch megamorphic, leaving TreeMap/TreeSet/ConcurrentSkipListMap's
compareTo dispatch monomorphic and the JDK's immutable Map.copyOf
(MapN) lookup site untouched -- both flagged by review as able to
artificially favor those structures in benchmark comparisons.

Adds polluteCompareToDispatch(), driven from polluteHashDispatch()
since every current caller wants both passes: each decoy key type
gets its own scratch TreeSet/TreeMap/ConcurrentSkipListMap (natural
ordering can't mix incomparable types in one instance, but the JVM's
call-site type profile is shared across instances regardless). Also
exercises a Map.copyOf-backed MapN with a get() pass, matching the
existing Set.copyOf-backed SetN handling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lution

Add J12 (checks.md) and a matching guide.md callout: a benchmark that
compares Map/Set implementations using only one key class leaves the
structure's hashCode()/equals()/compareTo call site monomorphic,
overstating its throughput relative to production -- exactly what
PR #12298 found (synchronized HashMap collapsed ~70% once dispatch
was polluted). Points reviewers at BenchmarkUtils.polluteHashDispatch().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ls-map-set-pollution

# Conflicts:
#	internal-api/src/jmh/java/datadog/trace/util/BenchmarkUtils.java
A one-shot BenchmarkUtils.polluteHashDispatch() call at @setup(Level.Trial)
or Level.Iteration) gets drowned out by the benchmark's own real-key traffic
before HotSpot compiles the shared internal dispatch call sites, letting them
re-specialize to a dominant receiver. Confirmed via -XX:+PrintInlining on
ImmutableSetBenchmark.hashSet_hitFresh: HashMap.getNode's key.equals(k) call
showed "no static binding" (genuinely virtual) only once pollution moved to
@setup(Level.Invocation), unlike the one-shot version.

Also fixes a transcription error in ThreadSafeMapD2Benchmark's pollution
delta prose: the unpolluted baseline quoted (971/935) didn't match this
file's own baseline table (777/769 ops/us).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the PollutionScratch/CompareToScratch persistent-scratch-object
machinery with a single warmUpHashDispatch(Blackhole) call: each pass now
allocates fresh collections and consumes the collection instances directly
via the Blackhole to defeat scalar replacement, instead of reusing
persistent fields. Removes the now-dead populateTypeProfile*/decoyKeys
varargs surface that had no callers, so using this in a new benchmark
needs nothing more than one @setup(Level.Trial) method with no scratch
object to construct or wire up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dougqh dougqh added type: feature Enhancements and improvements tag: no release notes Changes to exclude from release notes comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM labels Sep 25, 2026
@datadog-prod-us1-5

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.97 s 13.86 s [+0.1%; +1.5%] (maybe worse)
startup:insecure-bank:tracing:Agent 12.86 s 12.92 s [-1.2%; +0.3%] (no difference)
startup:petclinic:appsec:Agent 17.13 s 17.05 s [-0.6%; +1.5%] (no difference)
startup:petclinic:iast:Agent 16.82 s 16.91 s [-1.3%; +0.2%] (no difference)
startup:petclinic:profiling:Agent 16.55 s 16.71 s [-1.9%; -0.0%] (maybe better)
startup:petclinic:sca:Agent 17.13 s 16.87 s [+0.7%; +2.3%] (maybe worse)
startup:petclinic:tracing:Agent 16.13 s 16.14 s [-0.9%; +0.8%] (no difference)

Commit: c498d604 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

dougqh and others added 10 commits September 27, 2026 20:29
…n churn

Pollution now runs once per JVM rather than once per thread, so the nine
Scope.Thread benchmarks needed re-measuring. Zulu 17, -prof gc, same machine and
fork config as the previous run.

The interesting result is a negative one. Three files had been carrying
"warmUpHashDispatch allocates heavily before measurement, which can shift GC
state for the whole trial" as an explicitly unmeasured candidate for otherwise
unexplained drops. The guard produced an 8x reduction in that allocation with
everything else held constant, and nothing moved: update_hashMap 686.0 to 689.6,
update_hashtable 2770.7 to 2796.3, iterate_hashMap 19.83 to 19.84,
get_concurrentHashMap 1830.7 to 1837.1, get_support 2730.0 to 2731.5. All inside
their intervals. That candidate is now tested and ruled out rather than left
hanging, and HashtableD1 carries the comparison with D2 pointing at it.

HashtableD2's update margin lands at ~5.5x and its add arms remain a tie, now
with the hashtable ahead on the means but overlapping intervals. One arm moved
outside its interval -- D2 update_hashtable, 2492.7 to 2346.8 -- on the
zero-allocation path, so a GC explanation runs the wrong way; recorded as
run-to-run drift.

Refreshed throughput only for the four classes whose config is comparable
between the two runs. For CaseInsensitiveMap, ImmutableMap and SingleThreadedMap
the existing tables were taken at a different fork count, so only the allocation
figures are added -- bytes/op is deterministic and config-insensitive:

- CaseInsensitiveMap: lookup_hashMap is the only allocating lookup arm at
  25.5 B/op for the folded String, against zero elsewhere. The variance is real
  signal, since toLowerCase() returns this for already-lower-case input.
- ImmutableMap: the forEach-beats-iterator finding now has a mechanism --
  iterate_tagMap 40 B/op versus zero for iterate_tagMap_forEach.
- SingleThreadedMap: clone_tagMap 208 B/op against clone_hashMap 488.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Describe warmUpHashDispatch as broadening shared profiles rather than
making them megamorphic (receiver counts still drive C2's inlining),
drop the segment-based explanation for ConcurrentHashMap, and remove the
false claim that ConcurrentSkipListMap lacks computeIfAbsent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The once-per-thread vs once-per-JVM comparison shows the amount of
pollution work doesn't matter; it can't show whether a single pass
affects the trial, since both runs include one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unseeded per-fork key draws gave each arm a different case and hit/miss
mix. With a fixed sequence, HashMap's apparent ~28% lookup lead over
FlatHashtable disappears (384.7 +/- 81.0 vs 402.6 +/- 11.9), and the
folded-String allocation becomes deterministic. Default to @fork(5) to
match the documented configuration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he JDK

Replace the Java 8-to-17 allocator story with a controlled same-JDK
comparison (Zulu 17, forced FULL_DONTINLINE Blackhole, -prof gc):
disabling EliminateAutoBox changes nothing, while ParallelGC cuts
update_hashMap ~4x, matching JDK 8's default-collector number. The
mechanism is left unattributed since reported gc.time is similar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous seeded run's hashMap/treeMap arms showed intra-fork swings
consistent with external interference. A clean rerun on the merged code
(with the CHA_DEFEAT decoys) restores HashMap's ~20-30% lookup lead over
FlatHashtable; FlatHashtable's wide interval is one slow-compiling fork.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JMH forks a fresh JVM per @benchmark method, so each fork's C2 compiles
against a maximally specialized view: one arm's receiver types, one
arm's branch outcomes. warmUpArms drives every registered arm through
many shuffled-order passes before measurement, restoring the two-sided
branch profile a fork removes.

Arm0/Arm1/Arm2 adapters overload on standard java.util.function shapes
(Function, ToLongFunction, Predicate, Consumer, their Bi* counterparts,
plus TriFunction/TriConsumer for two-@State-parameter arms), so a
method reference or state-capturing lambda to an existing @benchmark
method coerces in directly, and overload resolution picks the
non-boxing primitive interface whenever the referenced method returns
an unboxed primitive.

Coverage is declared, not inferred: registering fewer arms than the
benchmark class's matching-shape @benchmark methods fails loudly at
setup time rather than silently warming a subset.

Jira: APMLP-1870

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…olerant overloads

Drops the primitive-specific overloads (ToLongFunction/Predicate/
ToLongBiFunction/BiPredicate) in favor of a single Function/Consumer
pair per arity plus TriFunction/TriConsumer for the arity-driven
shapes; a primitive result boxes on the way into Blackhole.consume,
which is warmup-only overhead off the measured path. Also adds a
fully reflective warmUp(Class<?>, Blackhole) overload that discovers
every @benchmark method on a class and drives it without any
registration, for benchmarks where one arm per method is enough.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
….warmUp works

Canonical case: a computeIfAbsent-guarded composite-key lookup where
ThreadSafeMapD2Benchmark's javadoc already documents that a one-sided
branch profile lets C2 prune the miss branch as unstable_if and
scalar-replace the composite key, understating real allocation. Both
@benchmark arms here call the identical getOrCreate bytecode on an
always-hit lookup; the only difference is whether warmUp primes that
same method with one genuine miss arm, on scratch data, before
measurement. Measured effect: 0 B/op with no priming vs 24 B/op (the
composite key's real size) with it, ~20% slower -- confirming warmUp
restores the production-realistic profile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dougqh
dougqh force-pushed the dougqh/benchmarkutils-warmup-arms branch from 5a5f2b1 to ec7bfa7 Compare October 6, 2026 17:30
@dougqh dougqh changed the title Add BenchmarkUtils.warmUpArms to restore cross-arm profile pollution Add BenchmarkUtils.warmUp to restore cross-arm branch profiles in benchmarks Oct 6, 2026
The class doc still said branch profiles were unaddressed. Also fix
claims that didn't match the code: a zero-arg lambda example that can't
fit Function<B, ?>, the demo's 'both @benchmark methods' (there is one,
with two @PARAM arms), TriFunction's arity, which @setup methods the
reflective path skips, and its never-set @PARAM fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dougqh dougqh changed the title Add BenchmarkUtils.warmUp to restore cross-arm branch profiles in benchmarks Add BenchmarkUtils.warmUp to restore cross-arm branch profiles in benchmarks (perf toolkit - benchmarking tools) Oct 7, 2026
dougqh and others added 5 commits October 8, 2026 14:33
JMH runs @setup on a generated Foo_jmhType subclass, so the bench's own
class declares no @benchmark methods and the check counted zero, letting
under-registration pass silently. Walk the hierarchy, counting an
overridden @benchmark method once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A benchmark class is often an @State with its own @setup; building it
without running that left its fields null and aborted the trial with an
NPE on the first reflective invocation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nested copies duplicated interfaces of the same shape already in
internal-api.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each miss pass inserted a fresh key into a per-thread scratch map that
stayed live through measurement, so pollute=true carried ~400k extra
entries the false arm didn't. Clear it after warmUp, and replace the
JDK 8 table with a Zulu 17 run: 0 vs 24 B/op, ~28% throughput cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The J12 rubric text said this PR measured FlatHashtableIteratorBenchmark, but
that benchmark is not part of the PR; the 37.9 -> 18.1 M ops/s numbers came
from an uncommitted experiment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dougqh and others added 3 commits October 13, 2026 08:30
BenchmarkUtils' class javadoc now keeps the fork, profile and escape-analysis
constraints while limiting production claims to what the benchmarks show.
HashtableD2Benchmark no longer says the remaining setup pass was ruled out:
eight passes vs one does not test its effect.

Co-authored-by: bric3 <bric3@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nchmarkutils-warmup-arms

# Conflicts:
#	internal-api/src/jmh/java/datadog/trace/util/BenchmarkUtils.java
@dougqh
dougqh changed the base branch from dougqh/benchmarkutils-map-set-pollution to master October 13, 2026 13:06

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant