From 9eb1e52de0385a239a1e4e4237e882cffcc9fe6c Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Fri, 3 Jul 2026 14:15:49 -0500 Subject: [PATCH 1/2] perf: reduce collector boundary test to a cached worst-bound compare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doc ids visit each per-query heap strictly ascending, so a candidate tying the worst kept hamming always loses the (hamming, doc_id) tie-break — once the collector is full, the accept test is exactly 'hamming < worst kept hamming'. Cache that bound in a register-friendly u32 (u32::MAX while filling) and skip the heap peek + tuple compare on the ~99.8% reject path. Bit-identical by construction; pinned by the tie-heavy and duplicate-run oracle suites. --- src/sign_bitmap.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/sign_bitmap.rs b/src/sign_bitmap.rs index b47f379..431d668 100644 --- a/src/sign_bitmap.rs +++ b/src/sign_bitmap.rs @@ -253,6 +253,13 @@ impl SignBitmap { let mut heaps: Vec> = (0..nq) .map(|_| BinaryHeap::with_capacity(m_eff + 1)) .collect(); + // Cached copy of each full heap's worst kept hamming. Doc ids visit + // each heap strictly ascending (d ascends within a row, blocks + // ascend), so a candidate tying the worst hamming always loses the + // (hamming, doc_id) tie-break — once full, the boundary test + // reduces to one u32 compare against this register. u32::MAX while + // filling (hamming <= dim can never reach it). + let mut worst_bounds = vec![u32::MAX; nq]; let mut block_start = 0usize; while block_start < n { @@ -266,14 +273,18 @@ impl SignBitmap { sign_scan_collect_batched(block, bn, qpv, qb_tile, tq, scores); for ti in 0..tq { let heap = &mut heaps[tile_start + ti]; + let worst = &mut worst_bounds[tile_start + ti]; let row = &scores[ti * bn..(ti + 1) * bn]; for (d, &hamming) in row.iter().enumerate() { - let key = (hamming, (block_start + d) as u32); - if heap.len() < m_eff { - heap.push(key); - } else if key < *heap.peek().expect("non-empty full collector") { + if hamming >= *worst { + continue; + } + heap.push((hamming, (block_start + d) as u32)); + if heap.len() > m_eff { heap.pop(); - heap.push(key); + } + if heap.len() == m_eff { + *worst = heap.peek().expect("full collector").0; } } } From 62eb4f60eca57f13620010051a90adcc0320da5d Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Fri, 3 Jul 2026 18:35:53 -0500 Subject: [PATCH 2/2] docs: scope the serial CSR contract to scan and selection Security-review note (fleet CIPHER-002): parallel finite validation introduced in the encode train transitively touches the global rayon pool from inside the 'serial' CSR primitive. The serial guarantee is about candidate scan/selection ownership, not input validation; say so explicitly. --- src/sign_bitmap.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sign_bitmap.rs b/src/sign_bitmap.rs index 431d668..170fd62 100644 --- a/src/sign_bitmap.rs +++ b/src/sign_bitmap.rs @@ -407,6 +407,11 @@ impl SignBitmap { /// to the historical per-query rescan. The CSR output contract is /// unchanged and bit-identical to the previous implementation. /// + /// "Serial" scopes the scan and selection: no rayon is entered for the + /// candidate work, so callers own that parallelism. Input finite- + /// validation MAY briefly use the global rayon pool for large query + /// buffers (order-independent boolean reduction; deterministic). + /// /// # Example /// ```no_run /// use ordvec::SignBitmap;