Add FlatHashtable open-addressed find-or-create collection#11980
Draft
dougqh wants to merge 9 commits into
Draft
Add FlatHashtable open-addressed find-or-create collection#11980dougqh wants to merge 9 commits into
dougqh wants to merge 9 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Contributor
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
dougqh
force-pushed
the
dougqh/flat-hashtable
branch
from
July 17, 2026 15:18
8e3862c to
2d79fe8
Compare
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
force-pushed
the
dougqh/flat-hashtable
branch
from
July 17, 2026 16:25
2d79fe8 to
07425af
Compare
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
force-pushed
the
dougqh/strategy-annotation
branch
from
July 17, 2026 16:28
4a05183 to
fbb0085
Compare
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
commented
Jul 17, 2026
dougqh
force-pushed
the
dougqh/flat-hashtable
branch
from
July 17, 2026 16:31
07425af to
b9c8860
Compare
dougqh
force-pushed
the
dougqh/strategy-annotation
branch
from
July 17, 2026 16:36
fbb0085 to
da64961
Compare
…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
force-pushed
the
dougqh/flat-hashtable
branch
from
July 17, 2026 16:44
b9c8860 to
5cb9a38
Compare
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
force-pushed
the
dougqh/flat-hashtable
branch
from
July 17, 2026 16:50
5cb9a38 to
f39ac32
Compare
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>
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 finalHelper(orStringHelper) that the JIT devirtualizes and inlines — C++-template-style static polymorphism: one algorithm, one monomorphic instantiation per call site.Helperis an abstract class on purpose (forces a named type;invokevirtualfallback if inlining ever misses);StringHelperseals a spreadhashso String-key callers write onlymatches/create.Pure mechanism —
capacityFor,create(typed backing array viaArray.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,
UTF8BytesStringcaches, …). 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 aspotlessApplypass) to confirm.🤖 Generated with Claude Code