Skip to content

Read transition arrays directly on the match path, bypassing SafeInde…#78

Open
MariusVolkhart wants to merge 1 commit into
mainfrom
jmh-harness
Open

Read transition arrays directly on the match path, bypassing SafeInde…#78
MariusVolkhart wants to merge 1 commit into
mainfrom
jmh-harness

Conversation

@MariusVolkhart

Copy link
Copy Markdown
Member

…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%

@snyk-io

snyk-io Bot commented Jun 23, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Base automatically changed from mv/benchmark to main June 29, 2026 16:50
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant