Skip to content

perf: share per-NFA BitState setup bundles across matchers (-25% warm-cache compile+find) - #146

Merged
jbachorik merged 3 commits into
mainfrom
perf/bitstate-table-bundle
Oct 5, 2026
Merged

jbachorik merged 3 commits into
mainfrom
perf/bitstate-table-bundle

Conversation

@jbachorik

@jbachorik jbachorik commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Shares all NFA-derived BitStateMatcher construction-time setup across matchers built from one cached NFA. The flattened per-state tables, greedy-loop shapes, single-first-ASCII prefilter, and the reject-DFA bundle (with its LazyDFACache) move into an immutable BitStateMatcher.Bundle; RuntimeCompiler.BitStateEntry and BitState-backed HybridEntry own one eager bundle published via putIfAbsent, and every matcher aliases its read-only fields. All mutable matcher state (capture buffers, job stacks, visited bitmap, counters, Laurikari instance, lazy PikeVM fallback) stays per-matcher; compile() still returns a fresh matcher per call.

Warm-cache per-operation compile()+find() drops from 4461.5 to 3333.3 ns/op (best-of-7 JMH, −25.3%), closing most of the remaining construction-cost gap on the backend's per-op matcher creation path.

Motivation

The profiling-backend wave-3 migration constructs generated matchers per operation through the runtime cache; per-construction table rebuilding was the measured dominant cost on the BitState path (cf. PR #139, which did the same for the PikeVM DfaBundle). Draft source: .issue-drafts/1-bitstate-table-sharing.md.

Related Issue(s)

Implements .issue-drafts/1-bitstate-table-sharing.md (draft measured headroom confirmed).

Change Type

  • Performance improvement
  • Test improvement

Checklist

  • I have read the CONTRIBUTING.md guidelines
  • All existing tests pass (./gradlew build)
  • I have added tests for my changes
  • I have updated documentation (if applicable)
  • My commits are signed

Performance Impact

Best-of-7 JMH (route-guarded fixture ^((?:x|y)+?)\.([^/]+), verified BitState-backed hybrid; 5×1s warmup, 5×1s measurement, 1 fork, sequential runs): baseline 4461.5 ns/op → 3333.3 ns/op, −1128 ns/op (−25.29%). All 14 raw JSON runs and the comparison retained in the session artifacts. Cold compilation, analysis, and budgets are untouched (BitState compile cost is analysis-side — kb/hyp-bitstate-blowup-root).

Additional Notes

SharedDfaBundleTest pins: sharing identity (standalone + hybrid), full mutable-state isolation, direct-ctor/counted-loop contracts, JDK-oracle API parity, clearCache() lifecycle, and entry publication + concurrent LazyDFACache population under contention. An independent concurrency-focused review verified safe publication and read-only discipline of every aliased table. Out of scope (filed separately): generated-DFA-half region/^ anchoring divergence on bounded matching with non-zero region start.

Rebase note (post #140)

Rebased onto 2ad23b7 (#140, soft-held shared NFA bundles) and reconciled: the full BitStateMatcher.Bundle is now soft-held per cache entry (BitStateEntry.bundle() / HybridEntry.bitStateBundle()) with #140's deterministic rebuild-on-miss; live matchers pin their bundle via a BitStateMatcher.sourceBundle reachability anchor (generalizing #140's reject-bundle anchor). The original eager-final publication guarantee is superseded by #140's equivalent-bundle-on-rebuild contract; #140's eviction tests were adapted to the full bundle and all tests pass (3278 green, 0 failures).

Measurement after rebase (same benchmark): the machine is currently too noisy for a precise figure (±10% run spread). Interleaved same-epoch A/B vs main@2ad23b7 (4 runs/side): median −13.1%, branch won the last 3 paired comparisons; best-of comparison vs the original 967e525 baseline remains −21.8%. Direction consistently favorable; will re-measure on a quiet machine before removing the Draft label.

Linux A/B (workspace-jb, 16-core x86_64, Zulu 26.0.2.1, quiet box)

Strictly interleaved runs, 7 per side, same machine/JDK (main@2ad23b7 vs this branch, route-guarded benchmark):

  • main ns/op: 19953 / 20333 / 20036 / 20246 / 21008 / 20152 / 20153 — best 19952.9, median 20153.4
  • branch ns/op: 18768 / 19836 / 19048 / 19858 / 19034 / 18909 / 19103 — best 18767.8, median 19047.7
  • best-of-7 −5.9%, median −5.5%, paired wins 7/7

Direction consistent with the macOS measurements; run spread on this box is ~3%. Raw JSONs for all 14 runs retained (linux-workspace-jb/).

@jbachorik jbachorik added the AI Generated or assisted by AI label Oct 5, 2026
@jbachorik
jbachorik marked this pull request as ready for review October 5, 2026 10:00
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T10:04:37.109838Z cc8f5a0 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-prod-us1-4 datadog-prod-us1-4 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.

Bits Code Review: FAIL

High-cardinality pattern compilation now strongly retains every shared BitState bundle and its lazy DFA storage in unbounded static caches, creating an avoidable heap-exhaustion path.

Open Bits AI session

🤖 Bits Code Review · Commit cc8f5a0 · @DataDog review to ask questions

// null — always the case for BITSTATE originals, which take the usePikeVm engine route). Built
// eagerly here, before the entry is published into HYBRID_CACHE, so every matcher from the
// winning entry shares one bundle identity. PikeVM and generated NFA halves never touch it.
final BitStateMatcher.Bundle bitStateBundle;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Soft-reference cached BitState bundles

Compiling many distinct BitState or BitState-backed hybrid patterns inserts entries into unbounded static caches. The new strong reference retains each full Bundle, including its growable LazyDFACache, until clearCache(), so high-cardinality workloads can exhaust the heap instead of allowing GC reclamation. Both entry types should soft-reference and rebuild these deterministic bundles.

Was this helpful? React 👍 or 👎
🤖 Bits Code Review · @DataDog review to ask questions · Open Bits AI session

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Resolved by the rebase onto 2ad23b7 (#140) — the finding was accurate for the pre-rebase revisions (de921d1/cc8f5a0, eager final bundle fields), but current head (98b1a6e+60b663a) soft-holds both bundle types exactly as this review prescribes:

  • BitStateEntry.bundle → volatile SoftReference<BitStateMatcher.Bundle> with deterministic rebuild-on-miss (RuntimeCompiler.java:349, accessor bundle() at :363)
  • HybridEntry.bitStateBundle → volatile SoftReference<BitStateMatcher.Bundle>, same rebuild contract (RuntimeCompiler.java:253, accessor bitStateBundle() at :304)

Live matchers pin their bundle via the BitStateMatcher.sourceBundle reachability anchor (generalizing #140's reject-bundle anchor), so eviction never affects in-flight matchers, and rebuilt bundles are equivalent because the build is a pure function of the NFA. #140's eviction tests (evictedPikeVmBundleIsRebuiltCorrectly, evictedBitStateBundleIsRebuiltCorrectly) pin the rebuild path, adapted to the full bundle.

What remains strongly held per entry is the entry itself (NFA + name map + SoftReference) in the pre-existing unbounded caches — unchanged by this PR and identical to the PikeVM/hybrid entries' contract since #139/#140.

Move all NFA-derived BitStateMatcher construction-time setup (flattened
tables, greedy-loop shapes, single-first-ASCII prefilter, reject-DFA bundle
with its LazyDFACache) into an immutable BitStateMatcher.Bundle. BitStateEntry
and BitState-backed HybridEntry own one eager bundle published via putIfAbsent;
matchers alias its read-only fields while all mutable state stays per-matcher.
Warm-cache compile drops ~25% per-op (4461 -> 3333 ns/op best-of-7 JMH on a
route-pinned GO-shaped hybrid fixture).
…nchmark

Sharing identity (standalone + hybrid), mutable-state isolation, direct-ctor
and counted-loop contracts, JDK-oracle API parity, clearCache lifecycle, entry
publication and LazyDFACache population under contention (latch-released
workers, capped at 8, fixed 40k total ops). The benchmark measures one
warm-cache compile()+find() and fails loudly if routing drifts away from the
BitState-backed hybrid.
@jbachorik
jbachorik force-pushed the perf/bitstate-table-bundle branch from cc8f5a0 to 3e7833d Compare October 5, 2026 10:17
@datadog-prod-us1-4

Copy link
Copy Markdown

Pipelines

✨ Unblock PR with BitsAI

❌ Errors

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 3 Pipeline jobs failed

CI | build (21) — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

DataDog/java-reggie | build — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

Security Scan | CodeQL analysis — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

Useful? React with 👍 / 👎

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

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.11538% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.9%. Comparing base (2ad23b7) to head (60b663a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../com/datadoghq/reggie/runtime/BitStateMatcher.java 96.8% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##              main    #146   +/-   ##
=======================================
  Coverage     84.9%   84.9%           
  Complexity       1       1           
=======================================
  Files          163     163           
  Lines        49002   49021   +19     
  Branches      7009    7009           
=======================================
+ Hits         41613   41634   +21     
+ Misses        5379    5378    -1     
+ Partials      2010    2009    -1     
Files with missing lines Coverage Δ
.../com/datadoghq/reggie/runtime/RuntimeCompiler.java 86.3% <100.0%> (-0.1%) ⬇️
.../com/datadoghq/reggie/runtime/BitStateMatcher.java 95.5% <96.8%> (+0.2%) ⬆️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2ad23b7...60b663a. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jbachorik
jbachorik merged commit a39ed40 into main Oct 5, 2026
9 checks passed
@jbachorik
jbachorik deleted the perf/bitstate-table-bundle branch October 5, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated or assisted by AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants