Skip to content

Scanner engine: dedup + candidate-filter perf - #5

Merged
ko1N merged 8 commits into
mainfrom
scanner-improvements
Jun 26, 2026
Merged

Scanner engine: dedup + candidate-filter perf#5
ko1N merged 8 commits into
mainfrom
scanner-improvements

Conversation

@killerra

Copy link
Copy Markdown
Collaborator

Scanner engine fixes from the code review, each as its own commit. All changes preserve
matching semantics — the anchor and span-hint are pure candidate-filter optimizations, and
the matches/finds cross-check proptests + fixture tests guard correctness.

Commits

  • SCAN-2 — use the precomputed anchor jump table in the finds path instead of rebuilding
    the 256-byte table per span.
  • SCAN-1 — unify the two duplicated scan-strategy families (Matches vs *_direct,
    ~250 lines); both paths now share one linear / first-byte / quick implementation.
  • SCAN-4 — seed ExecReader with the scanning span (via ExecScratch) so per-candidate
    verification skips the find_span binary search; out-of-span reads self-correct.
  • VM-2 — reuse one ExecReader across backtrack states instead of reconstructing it
    (and re-find_span-ing) per popped state.
  • SCAN-3 — add a reusable FindScratch + finds_prepared_with so repeated uniqueness
    scans don't reallocate executor scratch + probe buffer each call.
  • SCAN-8 — anchor on the most selective fixed-byte window anywhere in the linear
    prefix (across wildcards/skips/reads), not just the leading run.
  • Plus a scan_anchor benchmark for weak-leading / leading-wildcard patterns.

Benchmark (matches_code, median; cargo bench --bench scan_anchor)

Pattern PE64 before → after ELF64 before → after
48 8b ? ? ? ? 48 89 ? ? ba 2c (weak lead) 1.44 ms → 47.9 µs (−96.7%) 388 µs → 15.8 µs (−96.0%)
? ? 48 8b 0d ? ? ? ? 15 7c (leading wildcards) 20.1 ms → 1.02 ms (−94.9%) 8.0 ms → 240 µs (−97.0%)
48 8b ? ? ? ? ? ? 0f b6 84 (late rare run) 1.45 ms → 551 µs (−61.9%) 396 µs → 120 µs (−69.8%)
55 41 57 41 56 (strong-lead control) 1.49 ms → 1.40 ms (−6.4%) 621 µs → 584 µs (−5.9%)

The big wins are the anchor (SCAN-8). The ~6% on the strong-lead control — where the anchor
is unchanged — comes from the exec-overhead fixes (SCAN-4/VM-2 removing the per-candidate
find_span).

Verification

cargo test --workspace --all-targets --all-features, cargo test --workspace --doc,
cargo clippy --workspace --all-targets --all-features -- -D warnings -D clippy::all, and
cargo fmt --all -- --check all pass.

Not included

Remaining scanner findings (SCAN-5/6/7/9, VM-1/3/4) and the non-scanner PR (#4) are separate.

killerra added 8 commits June 26, 2026 01:52
Scans the PE64/ELF64 fixtures via matches_code with patterns whose strongest
literal run is not the leading one (leading-wildcard / late-rare), plus a
strong-leading control. Used to track candidate-filter improvements.
finds_unique_direct rebuilt the 256-byte Boyer-Moore skip table on every span.
Thread the precomputed PreparedPattern/PatternPlan anchor_jumps through the
direct quick-search path instead, matching what the Matches path already does.
Matches::next routed through its own copies of the linear / first-byte / quick
search strategies, duplicating ~250 lines against the finds path. Route it
through the shared find_next_in_span instead and delete the Matches copies; drop
the now-redundant _direct suffix since these are the sole implementation. The
matches/finds cross-check proptests confirm behavior is unchanged.
Every candidate verification built a fresh ExecReader whose constructor ran a
find_span binary search to locate the span the candidate was already inside.
Record the current span index in ExecScratch (set per span in find_next_in_span)
and seed ExecReader::new with it; find_span checks the seed first, so in-span
reads skip the binary search. Out-of-span reads (after a jump) self-correct.
exec_backtracking reconstructed an ExecReader (running find_span) for every
popped state. Build it once before the state loop and reuse it; the span cache
persists across states and self-corrects on jumps.
finds_unique allocated a fresh executor scratch and a second-match probe vec on
every call. Introduce a public FindScratch (executor scratch + probe buffer) and
finds_prepared_with so hot loops over many patterns can reuse it across calls;
finds_code/finds_prepared keep their simple signatures via a local FindScratch.
Anchor selection only scanned the leading byte run, so patterns with an early
wildcard (e.g. "? ? 48 8b 0d ...") got a weak anchor or none and degraded to a
near-linear scan. Build an offset map of known bytes across wildcards/skips/reads
and pick the most selective fixed-byte window anywhere in it. Pure filter; the
full pattern still verifies each candidate. Replaces build_prefix/select_anchor.
Add coverage for the offset-map anchor beyond the leading run: a skip that
pushes the distinctive run deep into the prefix (mapped + selected as a deep
anchor), the ANCHOR_MAP_CAP boundary (a run past the cap is dropped), and a scan
that still matches when the strong run is beyond the cap (weak anchor + exec).
@killerra
killerra force-pushed the scanner-improvements branch 2 times, most recently from da00958 to 76e68c5 Compare June 26, 2026 00:23
@ko1N
ko1N merged commit 415557f into main Jun 26, 2026
3 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.

2 participants