Skip to content

perf(search): early-exit synthetic verify + pool cap for --ast <pattern> (#394 AC11/#419)#420

Open
dean0x wants to merge 3 commits into
wave/wave4-searchfrom
ticket/394-ast-synthetic-markers
Open

perf(search): early-exit synthetic verify + pool cap for --ast <pattern> (#394 AC11/#419)#420
dean0x wants to merge 3 commits into
wave/wave4-searchfrom
ticket/394-ast-synthetic-markers

Conversation

@dean0x

@dean0x dean0x commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves the single failing acceptance criterion from the #394 Evaluator: AC11 (performance) was unmet because standalone --ast <synthetic-pattern> (deep-nesting, god-function, empty-function, empty-catch, excessive-params) was measured at ~6.9s on skim's own repo vs. the 500ms target (260x slower than --ast try-catch). Two root causes were fixed in tandem:

  1. Pool over-fetchCANDIDATE_POOL_FLOOR=100 was applied uniformly, so even a --limit 10 synthetic query fetched and gated 100 candidates instead of 10.
  2. Full traversal per candidate — the verification path called extract_ast_ngrams_with_metrics (complete O(n) traversal) for each candidate; for deep-nesting (DEEP_NODE → bucket_label(0)) the match fires at the first node at depth ≥ 4, so the traversal was doing needless work.

Changes

  • crates/rskim-search/src/ast_index/extract.rs: New synthetic_key_present(nodes, lang, target: AstBigram) -> bool — same ExtractState state machine as run_extraction (ADR-006 single-source), exits immediately when target is found in bigram_map. Skips emit_real_ngrams and update_branch_count (synthetic IDs cannot appear in real n-gram slots; correct per the BUCKET_LABEL_BASE contract).
  • crates/rskim-search/src/compound/reparse.rs: The synthetic branch (lines 366–393) now calls linearize_source + synthetic_key_present instead of the full extraction path, preserving the early-exit property inside the per-file verify gate.
  • crates/rskim/src/cmd/search/ast.rs: New ast_query_is_synthetic(query) -> bool helper; run_ast_standalone sizes the candidate pool to limit.max(1) for synthetic queries instead of max(K×limit, floor=100).
  • crates/rskim/src/cmd/search/ast_tests.rs: AC11 test ast_query_is_synthetic_classifies_correctly_ac11_394 asserts all 5 synthetic patterns → true, real-node patterns + containment → false.

Breaking Changes

None. FORMAT_VERSION stays 2. The verify gate call site in run_ast_standalone is unchanged; routing is internal to pattern_occurs_in_file.

Reviewer Focus Areas

  • ADR-006 compliance in synthetic_key_present: same ExtractState helpers (emit_depth_buckets, close_open_subtrees, fill_depth_gap, update_child_count, record_node) as run_extraction — drift is structurally impossible because both paths share the same methods.
  • Early-exit placement: check after emit_depth_buckets (fires for deep-nesting on first deep node) AND after close_open_subtrees (fires for god-function/empty-body/excessive-params when a subtree closes).
  • Pool gate: ast_query_is_synthetic uses is_synthetic_id from the crate — same predicate as AncestorMatchTable::contains_synthetic_id in reparse.rs, no duplicate threshold.
  • AC8 invariant: mixed (synthetic + real) patterns are rejected at the catalog level before this code runs; the pool gate and early-exit assume a pure-synthetic bigram set.

Closes #419 (the deferred AC11 follow-up filed in the original implementation commit).

Co-Authored-By: Claude noreply@anthropic.com

dean0x and others added 3 commits July 4, 2026 12:28
…euse (#394)

Standalone `--ast` returned zero results for all 5 synthetic-marker patterns
(god-function, deep-nesting, empty-function, empty-catch, excessive-params).
Root cause: vocab_lookup never yields a synthetic ID (>= BUCKET_LABEL_BASE),
making the strict real-CST ancestor walk in pattern_occurs_in_file
structurally unsatisfiable for them — while the compound path (which skips
this gate) returned matches on the same index, a standalone/compound
contradiction (avoids PF-006).

- pattern_occurs_in_file (AD-394-1/AD-394-2): route synthetic-containing
  patterns through re-running the index-time pipeline (linearize_source +
  extract_ast_ngrams_with_metrics) and checking every resolved n-gram key is
  present in the emitted set; real-node patterns keep the strict walk
  unchanged, preserving AD-374-6 precision (applies ADR-006: indexer is the
  single source of truth).
- recover_line (AD-394-5, OD-394-1 resolved to recover now): synthetic
  patterns recover a real representative :line via the same extraction pass
  instead of degrading to a path-only row.
- LinearNode gains transient start_line/start_byte (AD-394-6; PF-004: widen
  usize->u32 before saturating_add(1); no persisted-format change,
  FORMAT_VERSION stays 2 — AC12).
- is_synthetic_id (AD-394-3) is the single source of truth for the
  `>= BUCKET_LABEL_BASE` predicate, re-exported crate-wide; a new AC8 test
  asserts no catalog pattern mixes real + synthetic n-grams.

Ground-truth line values and the standalone/compound parity checks (AC1-AC13)
were verified via release-binary dogfood before being hardcoded into tests.
Filed #419 to track a discovered perf gap: the synthetic verify branch is
~260x slower than the real-node gate for low-selectivity patterns like
deep-nesting on a real corpus (6.9s vs 26ms) — correctness is unaffected;
deferred since a proper fix needs its own design.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the mutable-accumulator loop with a min_by_key iterator chain
— same semantics (first/topmost line wins on tie, matching AC-F3), half
the lines, no nested Some(match …).
…ttern> (#394 AC11/#419)

Replace full O(n) extract_ast_ngrams_with_metrics in the synthetic-marker
verify gate (pattern_occurs_in_file) with synthetic_key_present — an early-exit
variant using the same ExtractState emission logic (ADR-006 single source of
truth) that returns as soon as the target bigram key is confirmed present.

For deep-nesting (DEEP_NODE → bucket_label(0) fires at the first node at
CST depth >= 4), this exits on the very first qualifying node instead of
traversing the full node list.

Companion fix: reduce the candidate pool from max(5*limit, 100) to
limit.max(1) for synthetic patterns via ast_query_is_synthetic — synthetic
verify has near-zero drop rate so the 100-file floor was pure overhead.

Together: measured --ast deep-nesting latency drops from ~6.9s to within
AC11's <500ms target and the same order of magnitude as --ast try-catch.

Also removes the now-dead all_ngrams_present_in method from
AncestorMatchTable (compile-enforced dead-code elimination).

Unit test: ac11_394 classifies all 5 synthetic patterns as synthetic,
real-node patterns as real, and containment queries as real.

Closes AC11 of #394; fix tracked as #419.
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