Skip to content

Add StringIndex: a generic open-addressed string set - #11660

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 12 commits into
masterfrom
dougqh/tagset
Aug 21, 2026
Merged

Add StringIndex: a generic open-addressed string set#11660
gh-worker-dd-mergequeue-cf854d[bot] merged 12 commits into
masterfrom
dougqh/tagset

Conversation

@dougqh

@dougqh dougqh commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Adds StringIndex an open-indexed immutable hash set -- that can also work in conjunction with a separate data array as an immutable map.

In terms of ops/sec, StringIndex performs on par or better than HashSet and HashMap while consuming less memory.
In terms of ops/sec StringIndex out performs Set.of but consumes more memory.

StringIndex excels in situations where map values are primitive types or multiple map values are needed for the same keys.

Motivation

Data structure that can be reused throughout dd-trace-java that helps reduce tracer overhead - throughput impact and memory footprint

Additional Notes

StringIndex (datadog.trace.util, alongside the custom Hashtable) — a flat, allocation-free, open-addressed string set / index:

  • EmbeddingSupport — the static algorithm over raw int[] hashes / String[] names. Held in static final fields the refs fold to constants (the hot path).
  • Data — a build-time carrier {int[] hashes, String[] names} (pull into your own fields).
  • an instance wrapper for convenience (of/contains/indexOf).

2×-oversized (load factor ≤ 0.5), linear probe + wraparound, hash gates equals, interned == fast path, 0 = empty sentinel. Generic — no payload baked in; it just knows names. The headline capability is indexOf, which assigns each known string a stable dense slot; consumers attach a parallel array (e.g. long[] ids) indexed by that slot. Membership (contains) falls out as indexOf >= 0.

(Renamed from TagSet — the structure is more general than tags; a fixed name→id / membership index is just one of its uses.)

The static tier is named EmbeddingSupport and the sizing helper capacityFor, matching the rest of the flat-collections family (LightMap, Hashtable) so the "static functions over a caller-owned array/spine" tier is named identically across all of them.

Benchmarks

The motivating use is a fast, declared name→id table — a "pit of success" alternative to hand-rolled string switches and per-name caches — but the structure is general. Benchmarks (Apple M1, JDK 17, @Fork(5), @Threads(8); M ops/s):

  • ImmutableSetBenchmark (membership, hit): static EmbeddingSupport 2320HashSet 2198 > instance StringIndex 2098 > Set.copyOf/SetN 1914, and ~2.5–3.5× the array/sortedArray/treeSet forms. The folded EmbeddingSupport path is the fastest membership structure (~6% over HashSet); the instance wrapper costs ~10% (a field load), landing near HashSet.
  • ImmutableMapBenchmark (name→value get): static EmbeddingSupport 1498 (interned key 2081) > instance 1363 > HashMap 1216 > Map.copyOf/MapN 1049 — StringIndex-as-map is the fastest get, and a parallel long[]/int[] avoids the boxing a HashMap<String, Long> pays.
  • StringIndexSwitchBenchmark (name→id vs a hand-written string switch): on a runtime, varied key — the realistic regime — StringIndex is ~1.85× the switch (2147 vs 1161) and flat across inline/key-shape; the switch only matches it when the key is a compile-time constant (the JIT const-folds the switch away), which production keys never are.

Footprint (StringIndexFootprintTest, JOL): ~9% lighter than HashSet (no per-element Nodes) but ~27% heavier than Set.copyOf/SetN (it carries the cached int[] hashes + a 2×-oversized table) — so vs the JDK compact immutables the edge is speed + the indexOf→parallel-array capability, not footprint.

StringIndexTest covers hashing/zero-sentinel, probe + wraparound, table-full, and the parallel-payload usage.

🤖 Generated with Claude Code

@dougqh dougqh added 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: feature Enhancements and improvements labels Jun 17, 2026
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 95.65%
Overall Coverage: 58.40% (+0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1541293 | Docs | View more details | Give us feedback!

@dougqh dougqh changed the title Add TagSet: a generic open-addressed string set Add StringIndex: a generic open-addressed string set Jun 23, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jun 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 14.75 s 14.71 s [-0.6%; +1.1%] (no difference)
startup:insecure-bank:tracing:Agent 13.59 s 13.70 s [-1.8%; +0.2%] (no difference)
startup:petclinic:appsec:Agent 17.48 s 17.20 s [+0.7%; +2.6%] (maybe worse)
startup:petclinic:iast:Agent 17.45 s 17.62 s [-1.8%; -0.1%] (maybe better)
startup:petclinic:profiling:Agent 17.48 s 17.46 s [-1.0%; +1.3%] (no difference)
startup:petclinic:sca:Agent 17.25 s 17.25 s [-1.0%; +0.9%] (no difference)
startup:petclinic:tracing:Agent 16.58 s 16.65 s [-1.4%; +0.5%] (no difference)

Commit: 15412934 · 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 added a commit that referenced this pull request Jun 25, 2026
- Consume the StringIndex surface API now in #11660: FIXED_HANDLER_IDS via
  Support.mapIntValues; handlerId resolves via Support.lookup (id, or 0 on miss --
  the not-intercepted sentinel, so no separate slot check).
- Make TagInterceptor final (nothing extends it; aids JIT devirtualization).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh dougqh added the tag: performance Performance related changes label Jun 30, 2026
@dougqh
dougqh marked this pull request as ready for review June 30, 2026 20:16
@dougqh
dougqh requested a review from a team as a code owner June 30, 2026 20:16
@dougqh
dougqh requested a review from ygree June 30, 2026 20:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 767af3de1e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread internal-api/build.gradle.kts Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated

@bric3 bric3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, in general the generated comments, are not that readable. So I'd rather have human crafted comments.

E.g this way of aligning words is difficult to process, one sentence would be ok, but since all comments are like that :

 * <p>Slot 0-value is the empty sentinel: {@link Support#hash} never returns 0, so {@code hashes[i]
 * == 0} unambiguously means an empty slot.

Comment thread internal-api/build.gradle.kts Outdated
Comment thread internal-api/build.gradle.kts Outdated
@bric3 bric3 changed the title Add StringIndex: a generic open-addressed string set Add StringIndex: a generic open-addressed string set Jul 9, 2026
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated
@dougqh
dougqh changed the base branch from master to dougqh/tagmap-read-through-consumer July 15, 2026 19:41

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: PASS

More details

All 166 adversarial scenarios executed against the new StringIndex implementation pass cleanly — empty-string key (remapped hash 0xDD06), empty index, duplicate deduplication, full wraparound probe chains crossing different-hash occupants, parallel long[]/int[]/T[] payloads with realistic tracer tag sets, non-interned string lookups, and typed-array return from mapValues. The open-addressing algorithm (zero sentinel, linear probe, LF ≤ 0.5) is internally consistent and handles every edge case the diff introduces.

Was this helpful? React 👍 or 👎

📊 Validated against 166 scenarios · Open Bits AI session

🤖 Datadog Autotest · Commit 1d8fd80 · What is Autotest? · Any feedback? Reach out in #autotest

@dougqh
dougqh force-pushed the dougqh/tagmap-read-through-consumer branch from 3b9156e to d2aa48f Compare July 15, 2026 21:19
@bric3

bric3 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

coverage is violated:

Rule violated for class datadog.trace.util.StringIndex: instructions covered ratio is 0.7, but expected minimum is 0.8

@dougqh

dougqh commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

coverage is violated:

Rule violated for class datadog.trace.util.StringIndex: instructions covered ratio is 0.7, but expected minimum is 0.8

@bric3 Yes, I was working on plugging that gap yesterday. I added a few convenience methods at the end and that dropped the coverage level. I'll fix it.

@dougqh
dougqh force-pushed the dougqh/tagmap-read-through-consumer branch from d2aa48f to 1f257aa Compare July 20, 2026 15:43
@dougqh
dougqh requested a review from a team July 20, 2026 19:41
@PerfectSlayer
PerfectSlayer requested review from a team and vandonr and removed request for a team August 20, 2026 07:18

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: FAIL

Large capacity requests can return Integer.MIN_VALUE. This result violates the helper contract and can cause NegativeArraySizeException during allocation.

Open Bits AI session

🤖 Datadog Autotest · Commit 7c4f326 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated

@bric3 bric3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-approving, code looks good, but there's some light tweak around javadoc, and method overloads that take hashes or not.

Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/util/StringIndex.java
…actor

ceil(n / loadFactor) was narrowed to int before checking bounds, so a
sufficiently large n or small loadFactor silently saturated the narrowing
conversion and the subsequent << 1 wrapped to a negative capacity,
producing NegativeArraySizeException downstream instead of a clear error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dougqh
dougqh requested a review from a team as a code owner August 20, 2026 19:18
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: PASS

More details

The new index keeps the required open-addressing rules. The static review found no concrete failure in its set, index, mapping, or lookup behavior.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit c20ddd7 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

dougqh and others added 6 commits August 20, 2026 15:26
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
put is the only array-mutating entry point in EmbeddingSupport; keeping
it internal to the package (used only by create) enforces that callers
treat the arrays a StringIndex/EmbeddingSupport hands them as
build-once/immutable, and removes the raw-hash misuse surface a public
put would otherwise expose to external callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Aligns naming with EmbeddingSupport (renamed from Support for consistency
with the in-flight LightMap class) across benchmark methods, javadoc result
tables, and prose. No benchmarks re-run -- existing numbers are relabeled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The stringIndex_embedded rename left columns ragged; realign to the
new longest label.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dougqh
dougqh enabled auto-merge August 20, 2026 20:12
@dougqh
dougqh added this pull request to the merge queue Aug 20, 2026
@dd-octo-sts

dd-octo-sts Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 20, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-20 21:02:25 UTC ℹ️ Start processing command /merge


2026-08-20 21:02:29 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 2h (p90).


2026-08-20 21:21:07 UTCMergeQueue: The build pipeline contains failing jobs for this merge request

Build pipeline has failing jobs for deea3f2:

⚠️ Do NOT retry failed jobs directly (why?).

What to do next?

  • Investigate the failures and when ready, re-add your pull request to the queue!
  • If your PR checks are green, try to rebase/merge. It might be because the CI run is a bit old.
  • Any question, go check the FAQ.
Details

Since those jobs are not marked as being allowed to fail, the pipeline will most likely fail.
Therefore, and to allow other builds to be processed, this merge request has been rejected and the pipeline got canceled.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 20, 2026
JOL's GraphLayout relies on HotSpot-specific Unsafe internals and
throws IllegalStateException on IBM/Semeru JVMs, which are part of
the CI test matrix. Mirrors the guard already used in
ScopeAndContinuationLayoutTest.
@dougqh
dougqh added this pull request to the merge queue Aug 21, 2026
@dd-octo-sts

dd-octo-sts Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 21, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-21 14:47:36 UTC ℹ️ Start processing command /merge


2026-08-21 14:47:40 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-08-21 15:40:28 UTC ℹ️ MergeQueue: This merge request was merged

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 21, 2026
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit aa35904 into master Aug 21, 2026
593 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the dougqh/tagset branch August 21, 2026 15:40
@github-actions github-actions Bot added this to the 1.66.0 milestone Aug 21, 2026
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 tag: performance Performance related changes type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants