⏺ Results in. Cross-package, so benchstat can't auto-pair them, but the numbers speak clearly:
Lazy DFA vs NFA-only: same workload, different branches
BenchmarkLazyDFAHotPath — 50 shellstyle patterns, ~55 events, cache warmed:
┌───────────────────────┬───────────────────────┬──────┬────────┐
│ Branch │ sec/op │ B/op │ allocs │
├───────────────────────┼───────────────────────┼──────┼────────┤
│ main (NFA traversal) │ 62.05 µs ± 2% │ 0 │ 0 │
├───────────────────────┼───────────────────────┼──────┼────────┤
│ lazy_dfa (cached DFA) │ 13.42 µs ± 1% │ 0 │ 0 │
├───────────────────────┼───────────────────────┼──────┼────────┤
│ Δ │ −78.4% (4.62× faster) │ — │ — │
└───────────────────────┴───────────────────────┴──────┴────────┘
Eager vs Lazy: within lazy_dfa branch (same code, dfaTable toggled)
BenchmarkEagerVsLazy — 6 shellstyle patterns, 10 city events, 6 iterations:
┌─────────────────────────────────────────┬───────────────┬──────┬────────┐
│ Path │ sec/op │ B/op │ allocs │
├─────────────────────────────────────────┼───────────────┼──────┼────────┤
│ eager (pre-built DFA via traverseDFA) │ 2.339 µs ± 1% │ 0 │ 0 │
├─────────────────────────────────────────┼───────────────┼──────┼────────┤
│ lazy (dfaTable cleared → lazy fallback) │ 3.033 µs ± 0% │ 0 │ 0 │
├─────────────────────────────────────────┼───────────────┼──────┼────────┤
│ Δ │ +29.7% │ — │ — │
└─────────────────────────────────────────┴───────────────┴──────┴────────┘
The full speed hierarchy
eager DFA ████ (2.34 µs — ceiling)
lazy DFA █████ (3.03 µs — +30% over eager)
[scale break — different workload size]
lazy DFA █████████████ (13.4 µs — 50-pattern hot path)
NFA-only █████████████████████████████████████████████████████████████ (62 µs)
^-- 4.6× slower
Headline
- Lazy DFA over NFA: ~4.6× faster for moderately-complex shellstyle workloads. This is the big practical win — Quamina's current path leaves a lot on the table.
- Lazy DFA over Eager DFA: ~30% slower — the price of cache-budgeted on-demand construction vs full precompute. Acceptable given lazy is bounded (8 MB) while eager isn't.
So lazy DFA is roughly 4× better than NFA-only and only 30% worse than the theoretical ceiling. The +70% nfa2Dfa regression on build-context-extract is dead code; the bench that matters for users is LazyDFAHotPath, where lazy DFA dominates regardless of whether you embed smallTable or not.
The lazy_dfa branch is the one with the actual user-visible win in match-time perf. If Tim's looking for "what makes Quamina faster," the build-context-extract memory shrink is a nice modest win but lazy_dfa is the headline improvement.
If you want I can clean up the quamina-lazy worktree, or leave it around for further poking.