perf: query hot-path optimization stack (x86-validated) - #61
Conversation
WalkthroughRelease-profile and native benchmark settings were added, three primitive Criterion benches and a new baseline output were recorded, cached ChangesPerformance and hot-path updates
Bloom bit reduction
Debug-only panic test
Estimated review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
221b609 to
5c85b65
Compare
db9c356 to
9c45f4e
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
…nch harness Enables cross-crate inlining of the query hot path. target-cpu stays operator-driven via RUSTFLAGS (documented x86-64-v3 / Graviton floor); shipped artifacts remain portable. Records the post-profile bench baseline that subsequent perf PRs are measured against.
…ions) doubled_text_rejects_out_of_range_base asserts via debug_assert!, compiled out at opt-level=3, so it spuriously failed under `cargo test --release` (now run as a gate by the release-profile change). Gate the test on cfg(debug_assertions); the assertion semantics are unchanged in debug builds. Pre-existing latent issue, surfaced by adding the release-test gate.
fwd_qlen rescanned the read forward on every zigzag/reseed/pass-3 step (O(rlen^2) per read). Precompute next_n once per read in CollectScratch and read it O(1) from all three passes. Byte-identical (proptest: next_n[p] == fwd_qlen(read, p)).
ref_less/shares_prefix/lcp_at and the windowed model-locate closure recomputed keyed_compare_mask(query.len()) on every SA probe; the query slice is invariant for the whole find_boundary search, so compute (nbases, mask) once per search and pass it to compare_query_vs_suffix_2x_keyed_with_mask. Byte-identical (the _with_mask variant is proptest-equivalent to the recompute variant).
9c45f4e to
37338f5
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@prmi-sys/src/lib.rs`:
- Around line 961-966: The `copy_from_slice` path in
`out_steps_as_smemstep`/`SmemStep` relies on C ABI field order, but the current
size/alignment checks do not guard against field reordering. Add explicit
`offset_of!` assertions for `sa_start`, `occ_count`, and `match_len` in the
`SmemStep`/`prmi_smem_step_t` layout checks so the ABI contract is pinned even
if the struct fields are rearranged.
In `@prmi/src/sidecar/bloom_file.rs`:
- Around line 191-204: Update the stale comments around `bit_at` so their safety
invariant matches the new multiply-shift reduction. In `bit_at`, the bound comes
from Lemire reduction rather than `% num_bits`, so revise the SAFETY note near
the `contains` check to say `bit < num_bits` is guaranteed by the multiply-shift
mapping. Also adjust the writer guard comment in the related `contains`/write
path to remove the claim that `num_bits == 0` would panic in `bit_at`, and
instead describe the current behavior accurately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9bbd4f49-eac1-43b6-b5ac-e735a978597f
📒 Files selected for processing (10)
Cargo.tomlprmi-sys/src/lib.rsprmi/benches/README.mdprmi/benches/baselines/pr0.txtprmi/benches/primitives_bench.rsprmi/src/index/collect.rsprmi/src/index/spectrum.rsprmi/src/sa.rsprmi/src/sidecar/bloom_file.rsscripts/bench-native.sh
…, shrink lockstep key buffers A2: backward_spectrum_batch_impl mirrors the forward arm's zip (removes per-task bounds checks on the all_steps/tasks_s/out_ns triple). A3: write_fwd_task_steps and write_bwd_task_steps use copy_from_slice via the existing out_steps_as_smemstep helper instead of a field-by-field loop. A5: forward_boundary_windowed drops the always-true is_none_or guards on each bisection step (bisection monotonicity guarantees the most-recent probe is always the extreme; unconditional assignment is correct). A4: lockstep keyv Vec<Option<u64>> -> Vec<u64> + keys_present flag in all three lockstep loops (backward_spectrum_lockstep, forward_spectrum_lockstep, mem_search_lockstep); reduces per-element footprint from 16 B to 8 B. Byte-identical (lockstep/windowed equivalence oracles green, 253 tests passed).
…d loop The 32-base middle loop paid a try_into().unwrap() panic-branch and eight per-step out[] bounds checks (the i+4*j arithmetic defeats LLVM's proof from the i+32<=n guard). Drive it with chunks_exact_mut(32).zip(chunks_exact(8)) so bounds are checked once at the slice split with a known stride. Byte-identical (packed_word_decode + unaligned_word_step oracles green); no new unsafe. Add an inline before/after bench in primitives_bench.rs (unpack_packed_middle) that isolates the middle loop over a 1024-base / 256-corpus corpus. Measured ~35% wall-time reduction on aarch64 (19.5 µs → 12.5 µs, 13.4 → 20.9 Gelem/s); the real validation target is x86 (deferred EC2 batch).
The within-read sort ran two unstable sorts: stage 1 (m ASC, n DESC) then stage 2 (m ASC, n ASC). Stage 2 alone orders SMEMs with distinct (m, n); stage 1 could only reorder SMEMs sharing both m and n. Such a reorder is unobservable. Every emitted SMEM's (k, s) is mem_search(read[m..=n]), a deterministic function of the span content, so a fixed (m, n) determines a unique (k, s); with rid constant per read and l == 0 always, two entries sharing (m, n) are equal in all six fields. The multi-pass collection does emit (m, n) duplicates (a reseed or pass-3 round re-finding a pass-1 span), but each is a byte-identical copy, so permuting a tied group is the identity on the output bytes. Dropping stage 1 is therefore byte-identical to the C++ two-stage composition, including at the consumer box-gate (prmi's output bytes are unchanged). Add two regression guards: unsorted_mn_ties_are_byte_identical (a 4096-case proptest asserting every (m, n) tie in the unsorted set is byte-identical) and unsorted_mn_tie_census (a deterministic repeat-heavy corpus that finds 1440 tie-groups / 1764 duplicate entries across ~88k reads, all byte-identical).
37338f5 to
5828321
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@prmi/src/sidecar/bloom_file.rs`:
- Around line 59-72: Add a `.blm`-specific version check in the bloom sidecar
loading path so stale bloom files are rejected on open instead of being accepted
by the shared sidecar bind check. Update the logic around the `BloomFile`/`.blm`
reader and its bind validation (`sa_num`, `ref_digest`, `keyset_digest`) to also
verify a bloom-only version discriminator or equivalent metadata before using
`bloom_first`. Keep the check scoped to `.blm` so other sidecars are unaffected,
and fail fast when the bloom payload was built by an incompatible binary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 57d78cdf-14a2-4dee-b57d-75a428f6f3e6
📒 Files selected for processing (5)
prmi-sys/src/lib.rsprmi/benches/primitives_bench.rsprmi/src/index/collect.rsprmi/src/index/spectrum.rsprmi/src/sidecar/bloom_file.rs
Replace the per-probe `combined % num_bits` (true 64-bit div) with Lemire multiply-shift ((combined*num_bits)>>64), uniform into [0,num_bits) with no division; num_bits stays a multiple of 64 so sizing/header are unchanged. Writer and reader share bit_at so a self-consistent .blm has no false negatives (bloom_has_no_false_negatives green); FP rate stays near target (1.02%). Bench (1024 probes, num_bits=479296): before_mod: ~1.555 µs (~658 Melem/s) after_lemire: ~944 ns (~1084 Melem/s) ~1.65x throughput FORMAT NOTE: this changes which bits a key sets, so the .blm BODY bytes differ from a prior build (header layout unchanged; shared FORMAT_VERSION intentionally NOT bumped — it gates all sidecars). A .blm must be regenerated with a matching binary; a stale .blm yields benign bloom false negatives that the consumer's present-read fallback re-seeds (accelerator, not a correctness oracle). .blm body now diverges from #57's % reduction — flag for the #57 author at merge.
…cuous census guard, clippy-clean test targets - backward_spectrum_batch_impl: debug_assert_eq!(all_steps.len(), tasks_s.len()) for parity with the forward arm (zip would otherwise truncate silently on a length mismatch). - unsorted_mn_tie_census: assert tie_groups > 0 so the (m,n)-tie byte-identity guard cannot pass vacuously. - #[allow] the deliberately production-mirroring loops in the new proptests/bench so clippy --all-targets -D warnings is clean; production code already has zero warnings.
5828321 to
22119c6
Compare
Summary
Query hot-path performance pass over the SMEM search, model-locate, and bloom dispatch gate. Every commit is byte-identical to the prior behaviour except one deliberately format-revising bloom commit (
.blmbody bytes change; no false negatives — details below). The 9-commit stack is based onmain(#57's bloom dispatch gate is now merged); this PR targetsmain.All wins were validated on x86 (the deployment target) on ephemeral c7i (Sapphire Rapids, AVX-512) and c6a (Zen3, AVX2) instances, because the aarch64 dev box materially under-reports LTO/SIMD/division-removal effects. Each commit went through a two-stage review (spec compliance + adversarial code/byte-identity review) plus a final whole-branch review.
Measured results (x86, base → HEAD)
next_nmemoization (kernel)fwd_qlenrescans with one O(rlen) precomputebit_atreduction (kernel)divper bloom probe; larger Intel win (slow div)unpack_packed_forwardchunks_exact (kernel)codegen-units=1(whole search)target-cpuleft operator-driven (artifacts stay portable)Kernel ratios are isolated-primitive before/after; the LTO row is the end-to-end whole-search improvement.
Reading order (commit by commit)
codegen-units=1release profile + native bench harness. Re-baselines everything; the whole-search win above is mostly this.debug_assertshould_panictest behindcfg(debug_assertions)socargo test --releaseis green.CollectScratch(thenext_nkernel; O(rlen²)→O(1) across the three SMEM passes).chunks_exactrestructure ofunpack_packed_forward's word loop (the unpack kernel).sort_within_read(the(m,n)-tied entries are provably identical in all fields, so stage 1 only reordered byte-identical duplicates; kept regression-guard tests).present_anchor_bloomis unchanged from the base.bit_at(the Lemire kernel). Format-revising: changes which bits a key sets, so a.blmbody built by a prior binary differs. Header layout and the sharedFORMAT_VERSIONare unchanged (bumping it would invalidate every sidecar). Writer and reader sharebit_at, so a self-consistent.blmhas no false negatives. A stale cross-binary.blmis rejected on open via a newBLOOM_BODY_VERSIONfield (the previously-reserved header byte 20) — its bits would otherwise be stale, harmless on themem_search-confirmed any-window gate but surfaced as mis-routed reads on the unconfirmedbloom_firstgate. The sharedFORMAT_VERSIONis unchanged (a bloom-only reduction change must not invalidate other sidecars). A.blmis a build artifact regenerated per index build.debug_assert, a non-vacuous-guard assert on the tie census, and#[allow]s soclippy --all-targets -D warningsis clean (production code has zero warnings).Byte-identity
Every commit preserves SMEM output bit-for-bit except the Lemire
bit_atcommit (the bloom.blmbody, confined to the non-default bloom dispatch gate, no false negatives). New proptests/oracles guard the non-obvious cases:next_nvsfwd_qlen, the(m,n)-tie census,unpackvs scalar, and the bloom no-false-negatives round-trip.present_anchor_bloomis unchanged from the base (the prototyped roll was dropped, not included).Scope notes
target-cpuis intentionally not pinned; shipped artifacts stay portable (documentedx86-64-v3/ Graviton floor inprmi/benches/README.md, withscripts/bench-native.shfor host-tuned measurement).Testing
Full workspace suite green;
clippy --all-targets --all-features -- -D warningsclean. x86 micro + whole-search benches recorded per the table above.