introduce fast mode for optimized sibling presence checks
Centralize the layer count validation into PartitionCache and track it via a new fast_mode flag. Extend query tuples to include a pre-resolved destination layer index, enabling a fast-path batch lookup that bypasses per-layer probing when enabled. Refactor neighbor iteration and hit resolution to eliminate duplication and conditionally dispatch to the optimized path based on the cache state.
This commit is contained in:
@@ -136,8 +136,11 @@ struct GeneratedBatch {
|
||||
masks: Vec<FamilyMask>,
|
||||
/// Flat `slots.len() * n_genomes` — `genome_mask[i * n_genomes + g]`.
|
||||
genome_mask: Vec<u8>,
|
||||
/// `outgoing[dest_partition]` = `(variant, family_idx_in_batch, base)`.
|
||||
outgoing: Vec<Vec<(CanonicalKmer, usize, u8)>>,
|
||||
/// `outgoing[dest_partition]` = `(variant, family_idx_in_batch, base,
|
||||
/// layer)` — `layer` is the annex-recorded destination layer
|
||||
/// (`FamilyMask::family_members`'s `layer_value`, `0` if absent),
|
||||
/// trustworthy only when `PartitionCache::fast_mode()` is true.
|
||||
outgoing: Vec<Vec<(CanonicalKmer, usize, u8, u8)>>,
|
||||
_permit: ThrottleGuard,
|
||||
}
|
||||
|
||||
@@ -210,27 +213,27 @@ pub(super) fn scan_layer_families(
|
||||
let mut masks = Vec::with_capacity(n);
|
||||
let mut bases = Vec::with_capacity(n);
|
||||
let mut genome_mask = vec![0u8; n * ctx.n_genomes];
|
||||
let mut outgoing: Vec<Vec<(CanonicalKmer, usize, u8)>> = (0..ctx.n_parts).map(|_| Vec::new()).collect();
|
||||
let mut outgoing: Vec<Vec<(CanonicalKmer, usize, u8, u8)>> = (0..ctx.n_parts).map(|_| Vec::new()).collect();
|
||||
|
||||
// Pass 1: cheap, no matrix access — own base and this
|
||||
// batch's cross-partition queries. Kmer and mask already in
|
||||
// hand from `iter_minorants_batch`, no second annex read,
|
||||
// no slot lookup needed for this pass.
|
||||
// no slot lookup needed for this pass. `family_members`
|
||||
// (not a hand-rolled `central_canonical_neighbors` +
|
||||
// `mask.has` loop) already filters to present members and
|
||||
// hands back each one's annex-recorded layer alongside.
|
||||
for (i, entry) in batch.entries.iter().enumerate() {
|
||||
let (kmer, mask) = (entry.kmer, entry.mask);
|
||||
masks.push(mask);
|
||||
let base = central_base(kmer, ctx.k);
|
||||
bases.push(base);
|
||||
for other in kmer.central_canonical_neighbors() {
|
||||
if other == kmer {
|
||||
for (member, layer) in mask.family_members(kmer, ctx.k) {
|
||||
if member == kmer {
|
||||
continue; // local — resolved below straight from `mat`, no lookup
|
||||
}
|
||||
let b = central_base(other, ctx.k);
|
||||
if !mask.has(b) {
|
||||
continue;
|
||||
}
|
||||
let dest = other.partition(ctx.n_parts);
|
||||
outgoing[dest].push((other, i, b));
|
||||
let b = central_base(member, ctx.k);
|
||||
let dest = member.partition(ctx.n_parts);
|
||||
outgoing[dest].push((member, i, b, layer.unwrap_or(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,10 +283,16 @@ pub(super) fn scan_layer_families(
|
||||
// module docs), each thread owning one partition's queries
|
||||
// contiguously until this batch is done.
|
||||
let genome_mask: Vec<AtomicU8> = batch.genome_mask.into_iter().map(AtomicU8::new).collect();
|
||||
let fast_mode = cache.fast_mode();
|
||||
batch.outgoing.par_iter().enumerate().filter(|(_, q)| !q.is_empty()).for_each(|(dest, queries)| {
|
||||
cache.find_presence_batch(dest, queries, n_genomes, |i, base, g| {
|
||||
let on_hit = |i: usize, base: u8, g: usize| {
|
||||
genome_mask[i * n_genomes + g].fetch_or(1 << base, Ordering::Relaxed);
|
||||
});
|
||||
};
|
||||
if fast_mode {
|
||||
cache.find_presence_batch_fast(dest, queries, n_genomes, on_hit);
|
||||
} else {
|
||||
cache.find_presence_batch(dest, queries, n_genomes, on_hit);
|
||||
}
|
||||
});
|
||||
|
||||
for i in 0..n {
|
||||
|
||||
Reference in New Issue
Block a user