Read transition arrays directly on the match path, bypassing SafeInde…#78
Open
MariusVolkhart wants to merge 1 commit into
Open
Read transition arrays directly on the match path, bypassing SafeInde…#78MariusVolkhart wants to merge 1 commit into
MariusVolkhart wants to merge 1 commit into
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
2c8acfc to
b2654f5
Compare
f82ca08 to
709a154
Compare
…xable dispatch The per-character match loop reached every transition, value, and prefix through AhoCorasickStore -> a SafeIndexable field. SafeIndexable is abstract with two implementations (DoubleArrayIntList during build, UnboxedIntList after), so each access was a bimorphic virtual call plus a backing-array field load plus a bounds check. That indirection sat in the hottest loop in the library. After build() the store is always backed by UnboxedIntList, so the match path can read the raw int[] directly: monomorphic, and the JIT can hoist the array reference and eliminate per-access bounds checks. The build-time and keyValue paths still go through the SafeIndexable accessors, so pre-build mutation (add/replace, which run before buildRuntimeStructures) is unchanged. Mechanical changes: - Expose UnboxedIntList.backingArray. - AhoCorasickStore.buildRuntimeStructures captures the five backing arrays (runtimeBaseOffsets/Parents/Values/FailureIndices/PrefixIndices) once the lists have been converted to UnboxedIntList. - calculateNextState reads baseOffsets/parents/failureIndices directly; the parentCount bounds check reproduces safeGetParent (out-of-range child has no parent, and RESERVED_VALUE can never equal a non-negative node index). - AhoCorasickSpliterator captures the value/prefix arrays once and indexes them per result. Behavior unchanged (full library test suite green). Allocation per op unchanged, as expected — this removes dispatch, not allocation. ParseBenchmark, 100k dictionary, JDK 25, f2/wi5/i10 (ops/s, higher is better): config before after delta ASCII SPARSE 38.5±0.7 43.8±0.8 +13.6% ASCII DENSE 12.0±0.2 13.7±0.4 +14.6% UNICODE SPARSE 71.4±1.7 81.2±1.5 +13.8% UNICODE DENSE 12.0±0.1 13.2±0.2 +9.6% Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
709a154 to
08a1dd9
Compare
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.
…xable dispatch
The per-character match loop reached every transition, value, and prefix through AhoCorasickStore -> a SafeIndexable field. SafeIndexable is abstract with two implementations (DoubleArrayIntList during build, UnboxedIntList after), so each access was a bimorphic virtual call plus a backing-array field load plus a bounds check. That indirection sat in the hottest loop in the library.
After build() the store is always backed by UnboxedIntList, so the match path can read the raw int[] directly: monomorphic, and the JIT can hoist the array reference and eliminate per-access bounds checks. The build-time and keyValue paths still go through the SafeIndexable accessors, so pre-build mutation (add/replace, which run before buildRuntimeStructures) is unchanged.
Mechanical changes:
Behavior unchanged (full library test suite green). Allocation per op unchanged, as expected — this removes dispatch, not allocation.
ParseBenchmark, 100k dictionary, JDK 25, f2/wi5/i10 (ops/s, higher is better):
config before after delta
ASCII SPARSE 38.5±0.7 43.8±0.8 +13.6%
ASCII DENSE 12.0±0.2 13.7±0.4 +14.6%
UNICODE SPARSE 71.4±1.7 81.2±1.5 +13.8%
UNICODE DENSE 12.0±0.1 13.2±0.2 +9.6%