Skip to content

Instantly share code, notes, and snippets.

@sayrer
Last active May 31, 2026 01:17
Show Gist options
  • Select an option

  • Save sayrer/1bcd6364d08305c8c3136a7e2d65d09c to your computer and use it in GitHub Desktop.

Select an option

Save sayrer/1bcd6364d08305c8c3136a7e2d65d09c to your computer and use it in GitHub Desktop.
benchmarks.md

⏺ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment