Skip to content

perf: cut elementary allocation and algorithmic tech debt#462

Merged
zharinov merged 1 commit into
masterfrom
perf/elementary-tech-debt
Jun 14, 2026
Merged

perf: cut elementary allocation and algorithmic tech debt#462
zharinov merged 1 commit into
masterfrom
perf/elementary-tech-debt

Conversation

@zharinov

@zharinov zharinov commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Each checkpoint carries a running max of the frame index it needs. Popping reads that value directly, so backtracking never scans the checkpoint stack. A query that backtracks hard over a 740 KB file runs in ~0.25s (about 19x faster).
  • JSON output is written straight into the output string. Escaping, span numbers, and indentation append in place instead of building a small throwaway String each time. Formatting a whole result does about 17 allocations.
  • A node's kind is kept as a &'static str, since tree-sitter already owns that name. Struct objects reserve their field slots up front. Materializing a result does about 25% fewer allocations.
  • Field, definition, variant, and ref resolution use the hash maps that already exist instead of looping over every entry.
  • Smaller wins: the interner uses a faster hash, the lexer reserves space for its tokens, successors() returns a borrowed slice, the string table shares each string with Rc<str>, layout scores are computed once, and the type sort uses a heap.
  • Grammar loading resolves names through a map and builds the "used symbols" set once.

Why

A performance review, double-checked by a second pass, found easy wins: one quadratic on the backtracking path, a lot of throwaway allocations when printing results, and many loops that could be a single map lookup. All 1068 tests pass. Nothing changes behavior.

Notes

  • The checkpoint change is the important one. The max-frame value used for pruning rides along on each checkpoint, so it stays correct without a scan. New unit tests check that value across pushes and pops.
  • Skipped on purpose, too big or too risky for one perf PR:
    • Borrowing materializer field names into Value (would add a lifetime to the public Value type).
    • Caching the linker's admissibility sets (needs new fields and &mut self in the diagnostics code).
    • mem::take in grammar node-shape code and per-body type inference — both change results, not just speed.
    • Reading bytecode modules straight into the aligned buffer (needs unsafe, and the CLI doesn't use that path).
    • Regex DFAs are rebuilt on each predicate check — already tracked in Regex predicate DFAs are deserialized on every evaluation #426.

VM runtime
- checkpoint: maintain max_frame_ref via a per-checkpoint prefix-max instead
  of an O(n) stack rescan on backtrack (was O(N^2) on any backtracking-heavy
  query; ~19x faster on a deep-backtrack query over a 740 KB file)
- value/materializer: write JSON output directly into the buffer (escape, span
  ints, indentation), store NodeHandle.kind as &'static str, reserve struct
  field capacity — output formatting allocations drop from ~20k/48k to ~17

Compiler / core
- replace linear scans with existing O(1) lookups (field/def/variant/ref
  resolution); FxHashMap for the interner; lex() capacity hint
- InstructionIR::successors() returns a borrowed slice; defer laser_vision
  clones; share string-table allocations via Rc<str>; precompute layout block
  scores; topo-sort via a max-heap
- grammar load: intern names through a map; precompute the used-symbol set

No behavior change; adds checkpoint max_frame_ref invariant tests.
@zharinov
zharinov merged commit 543d9a3 into master Jun 14, 2026
5 checks passed
@zharinov
zharinov deleted the perf/elementary-tech-debt branch June 14, 2026 09:50
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