Skip to content

perf: DFA FindAll early termination + prefilter skip#129

Merged
kolkov merged 2 commits intomainfrom
feature/dfa-findall-early-termination
Mar 8, 2026
Merged

perf: DFA FindAll early termination + prefilter skip#129
kolkov merged 2 commits intomainfrom
feature/dfa-findall-early-termination

Conversation

@kolkov
Copy link
Contributor

@kolkov kolkov commented Mar 8, 2026

Summary

  • DFA.IsMatchAt() with early termination — New method stops at first match state (O(k)) instead of scanning for longest match (O(n)). Used in findIndicesDFAAt as prefilter check, eliminating O(n²) total scanning in FindAll on dense-match inputs.
  • Prefilter skip for PikeVM — When prefix prefilter is available (e.g., memmem for {{), jump PikeVM directly to the candidate position instead of scanning from at. Avoids PikeVM processing non-matching regions.
  • Template pattern \{\{(.*?)\}\} FindAll improved ~37% (5.4ms → 3.4ms on 50KB with 2000 matches).
  • Remaining 2.8x gap vs stdlib is PikeVM per-byte cost (UTF-8 . handling) — separate optimization task.

Follow-up to #128 (BoundedBacktracker span fix). Both reported by @kostya via #124.

Test plan

  • go test ./... — all 9 packages pass
  • gofmt -l . — no formatting issues
  • Key benchmarks verified: no regressions in Find, IsMatch, FindAll patterns
  • CI: tests + benchmark comparison

kolkov added 2 commits March 9, 2026 00:28
…inputs

findIndicesDFAAt used DFA.FindAt (longest-match O(n) scan) as prefilter,
then PikeVM re-scanned for exact bounds. For FindAll on dense-match inputs
(e.g., template patterns with 2000 matches over 50KB), total DFA work
was ~50MB due to O(n^2) scanning.

Fix:
- Added DFA.IsMatchAt() with early termination — O(k) where k is
  distance to first match, vs O(n) for longest-match scan
- Prefilter skip: when prefix prefilter available, jump PikeVM directly
  to candidate position instead of scanning from 'at'

Template pattern FindAll improved ~37% (5.4ms -> 3.4ms).
Remaining 2.8x gap vs stdlib is PikeVM per-byte cost (UTF-8 dot handling).
@codecov
Copy link

codecov bot commented Mar 8, 2026

Codecov Report

❌ Patch coverage is 65.00000% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
dfa/lazy/lazy.go 0.00% 6 Missing ⚠️
meta/find_indices.go 92.85% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

Benchmark Comparison

Comparing main → PR #129

Summary: geomean 121.7n 121.8n +0.04%

⚠️ Potential regressions detected:

geomean                    121.7n         121.8n        +0.04%
geomean                               ³                +0.00%               ³
geomean                               ³                +0.00%               ³
geomean              33.66n         33.66n        +0.01%
geomean                         ³                +0.00%               ³
geomean                         ³                +0.00%               ³
ASCIIOptimization/WithoutASCII-4                        10.63n ± ∞ ¹    10.71n ± ∞ ¹    +0.75% (p=0.048 n=5)
ASCIIOptimization_Issue79/short_WithASCII-4             279.0n ± ∞ ¹    279.6n ± ∞ ¹    +0.22% (p=0.008 n=5)
DNA_VsStdlib/stdlib/dna_5-4                             56.67m ± ∞ ¹    58.37m ± ∞ ¹    +3.00% (p=0.032 n=5)
BranchDispatch_Coregex/UUID-4                           7.050n ± ∞ ¹    7.190n ± ∞ ¹    +1.99% (p=0.008 n=5)

Full results available in workflow artifacts. CI runners have ~10-20% variance.
For accurate benchmarks, run locally: ./scripts/bench.sh --compare

@kolkov kolkov merged commit 90c3f64 into main Mar 8, 2026
8 of 9 checks passed
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