Skip to content

Add FlatHashtable open-addressed find-or-create collection#11980

Draft
dougqh wants to merge 9 commits into
dougqh/strategy-annotationfrom
dougqh/flat-hashtable
Draft

Add FlatHashtable open-addressed find-or-create collection#11980
dougqh wants to merge 9 commits into
dougqh/strategy-annotationfrom
dougqh/flat-hashtable

Conversation

@dougqh

@dougqh dougqh commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

From Claude:

A reusable open-addressed table over self-contained entries — one reference per slot, so entry publication is a single reference store and readers see null-or-complete (no torn reads, no volatile/atomics needed when the payload makes a stale/lost read benign).

Callers specialize it via a concrete-typed static final Helper (or StringHelper) that the JIT devirtualizes and inlines — C++-template-style static polymorphism: one algorithm, one monomorphic instantiation per call site. Helper is an abstract class on purpose (forces a named type; invokevirtual fallback if inlining ever misses); StringHelper seals a spread hash so String-key callers write only matches/create.

Pure mechanism — capacityFor, create (typed backing array via Array.newInstance), get, getOrCreate. Cardinality cap / overflow / a live-size counter are caller policy.

Intended as the shared backing for several open-addressed caches/tables (per-operation sizing hints, UTF8BytesString caches, …). This PR is just the standalone collection + unit tests; callers land separately.

Draft caveat: authored without a local module build (to stay off an in-progress release), so spotlessCheck / spotbugsMain / compile want CI (or a spotlessApply pass) to confirm.

🤖 Generated with Claude Code

@dougqh dougqh added comp: core Tracer core tag: no release notes Changes to exclude from release notes type: refactoring tag: ai generated Largely based on code generated by an AI or LLM labels Jul 16, 2026
@datadog-prod-us1-6

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Jul 16, 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.96 s 13.89 s [-0.1%; +1.1%] (no difference)
startup:insecure-bank:tracing:Agent 12.86 s 12.97 s [-1.6%; -0.1%] (maybe better)
startup:petclinic:appsec:Agent 16.85 s 16.69 s [-0.1%; +2.0%] (no difference)
startup:petclinic:iast:Agent 16.88 s 17.05 s [-1.9%; -0.2%] (maybe better)
startup:petclinic:profiling:Agent 16.71 s 16.76 s [-1.5%; +0.8%] (no difference)
startup:petclinic:sca:Agent 16.94 s 16.84 s [-0.5%; +1.6%] (no difference)
startup:petclinic:tracing:Agent 16.02 s 15.71 s [-2.2%; +6.2%] (no difference)

Commit: f9f9d224 · 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
dougqh force-pushed the dougqh/flat-hashtable branch from 8e3862c to 2d79fe8 Compare July 17, 2026 15:18
@dougqh
dougqh changed the base branch from master to dougqh/strategy-annotation July 17, 2026 15:18
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java Outdated
@dougqh
dougqh force-pushed the dougqh/flat-hashtable branch from 2d79fe8 to 07425af Compare July 17, 2026 16:25
Comment thread internal-api/src/test/java/datadog/trace/util/FlatHashtableTest.java Outdated
Comment thread internal-api/src/test/java/datadog/trace/util/FlatHashtableTest.java Outdated
Comment thread internal-api/src/test/java/datadog/trace/util/FlatHashtableTest.java Outdated
@dougqh
dougqh force-pushed the dougqh/strategy-annotation branch from 4a05183 to fbb0085 Compare July 17, 2026 16:28
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java
Comment thread internal-api/src/main/java/datadog/trace/util/FlatHashtable.java Outdated
@dougqh
dougqh force-pushed the dougqh/flat-hashtable branch from 07425af to b9c8860 Compare July 17, 2026 16:31
@dougqh
dougqh force-pushed the dougqh/strategy-annotation branch from fbb0085 to da64961 Compare July 17, 2026 16:36
dougqh and others added 2 commits July 17, 2026 12:37
…hism helpers

A reusable open-addressed table over self-contained entries (one reference per
slot), designed so callers specialize it via a concrete-typed static-final
Helper that the JIT devirtualizes and inlines (C++-template-style static
polymorphism). Cardinality cap / overflow / size are caller policy; this class
is pure mechanism (capacityFor, create, get, getOrCreate). Includes a
String-key StringHelper that seals a spread hash.

Intended as the shared backing for several open-addressed caches/tables
(per-operation sizing hints, UTF8BytesString caches, etc.).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Force hash collisions via fixed-hash helpers to exercise the linear-probe
paths the original tests missed: probe-past-occupied + match-after-probe (in
both get and getOrCreate), wraparound to the front, and get()'s full-table
wrap-to-null branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqh force-pushed the dougqh/flat-hashtable branch from b9c8860 to 5cb9a38 Compare July 17, 2026 16:44
KeyStrategy (abstract class): hash/matches/hashOf, long hashes for family consistency with Hashtable/ConcurrentHashtable. StringKeyStrategy seals the spread hash; EntryKeyStrategy seals hashOf to a cached Entry.hash (Entry is an optional structure-free base).

CreateStrategy (bespoke @FunctionalInterface): create, cold+per-use, lambda-able. @strategy on both strategy types; @StrategyConsumer on the inlining consumers (get/getOrCreate/insert).

Surface: get/getOrCreate (key-taking) + two insert flavors (Entry-based / KeyStrategy-based) over a shared comparison-free placement core; forEach (+ context variant); hash-filtered read-only iterator (not a StrategyConsumer -- interface-dispatched cold traversal). No removal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqh force-pushed the dougqh/flat-hashtable branch from 5cb9a38 to f39ac32 Compare July 17, 2026 16:50
Adds FlatHashtable to SingleThreadedMapBenchmark on the ops it supports (build via comparison-free insert, get, iterate) — its self-contained entry holds the value unboxed vs HashMap<String,Integer>. Fixed-capacity so the table is sized to the key count. Uses the INSTANCE-singleton strategy style (private ctor, lazy class-init).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dougqh and others added 5 commits July 17, 2026 13:52
…rison

Adds FlatHashtable to ThreadSafeMapBenchmark: build + concurrent get on a shared, once-published table (lock-free, no volatile) alongside ConcurrentHashMap / volatile-HashMap / synchronizedHashMap. Fixture mirrors SingleThreadedMapBenchmark (self-contained per benchmark).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unter contention)

nextLookupKey used a shared static counter incremented from all @threads(8) — a cache-line-contended race that floors the fastest reads and masks the very differences the benchmark compares (mirrors the per-thread index SingleThreadedMapBenchmark already uses, and the set-benchmark fix in #11721). Maps stay static/shared; only the lookup index goes per-thread via @State(Scope.Thread). Existing Javadoc numbers predate this and need a rerun.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each concrete key strategy carries a static final INSTANCE of its exact type + a private ctor (lazy class-init singleton); call sites use X.INSTANCE, dropping the separate module-level constants. Matches the benchmark fixture and the strategy-class style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…virt

Load two decoy KeyStrategy implementors alongside the real one in both map
benchmarks so KeyStrategy.hash and KeyStrategy.matches each have >=2 concrete
implementors before the hot method compiles. This denies C2 the single-
implementor CHA devirtualization of keyStrat.hash/matches inside get(), so the
steady-state numbers reflect the no-CHA regime rather than a deopt-guarded bet.

Verified with -XX:+PrintInlining (Zulu 21; Java 8/ARM64 is fine for inlining-
decision inspection but not for throughput): with CHA impossible, the concrete
StringKeyStrategy::hash / IntEntryKeyStrategy::matches still inline (hot) and no
type-profile/morphic markers appear on any FlatHashtable/KeyStrategy method. The
devirtualization is therefore exact-type, from the constant INSTANCE propagated
through the inlined get -- the structural monomorphization the @strategy
contract promises, not a CHA or type-profile speculation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tHashtable

Rounds out the FlatHashtable toolbox and adds a case-insensitive map arm to the
comparison benchmark:

- Strings.caseInsensitiveHashCode: a hashCode consistent with
  String.equalsIgnoreCase (same two-way fold as regionMatches(ignoreCase)),
  allocation-free. Reusable primitive; the strategy below composes it.
- FlatHashtable.CaseInsensitiveStringKeyStrategy: case-insensitive sibling of
  StringKeyStrategy, sealing hash to the primitive.
- The table now owns the spread (home()): a golden-ratio Fibonacci mix robust to
  weak/int-derived and full 64-bit hashes alike, so a KeyStrategy returns a plain
  hashCode without pre-mixing. StringKeyStrategy/CI/Entry now carry raw hashes.
- Load-factor control: DEFAULT_LOAD_FACTOR (0.5) / LOW_LOAD_FACTOR (0.25)
  constants plus create(Class,int,float) / capacityFor(int,float); the 2-arg
  forms are kept and delegate to the default.
- resize(...) and resizingInsert(...) (Entry and KeyStrategy flavors): explicit,
  caller-invoked growth for the rare full case, keeping get/insert resize-free on
  the hot path. resizingInsert returns the (possibly new) array either way.

Benchmark: CaseInsensitiveMapBenchmark gains a FlatHashtable arm (dogfooding the
shared strategy + primitive) and a DEFAULT-vs-LOW load-factor pair, and moves the
lookup index per-thread (@State(Scope.Thread)) so the shared counter doesn't floor
the fastest reads. On Zulu 21 the CI FlatHashtable is ~2x the (previously
recommended) TreeMap at the same zero allocation, and matches HashMap's throughput
without HashMap's per-lookup folded-String garbage; LOW_LOAD_FACTOR is a wash for
the fold-dominated CI lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant