Push zunrplorkwkt #70

Merged
coissac merged 93 commits from push-zunrplorkwkt into main 2026-08-28 23:15:38 +00:00
9 changed files with 180 additions and 145 deletions
Showing only changes of commit ac38aa759b - Show all commits
+62 -6
View File
@@ -300,7 +300,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af737c6c59cb018ecbe6472cbdf86d39c59d78252febfe311953a991b6e4ed85" checksum = "af737c6c59cb018ecbe6472cbdf86d39c59d78252febfe311953a991b6e4ed85"
dependencies = [ dependencies = [
"common_traits", "common_traits 0.11.4",
"epserde", "epserde",
"mem_dbg", "mem_dbg",
] ]
@@ -475,7 +475,18 @@ checksum = "fda9ae1f26adcae83adb2e92f69cf59421f2a277a942f49f8e59f2fcbd7cf062"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"half", "half",
"impl-tools", "impl-tools 0.10.3",
]
[[package]]
name = "common_traits"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e0d7d0c5977b1e87f759d406740c38227125f374f54d81bc197e877f2c5f26"
dependencies = [
"anyhow",
"half",
"impl-tools 0.12.0",
] ]
[[package]] [[package]]
@@ -783,7 +794,7 @@ checksum = "c40d342ff20a2ce62d9a85ce406e672dfa137f902ac9670034533184f1533976"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.1", "bitflags 2.11.1",
"common_traits", "common_traits 0.11.4",
"epserde-derive", "epserde-derive",
"maligned", "maligned",
"mem_dbg", "mem_dbg",
@@ -1083,11 +1094,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ae95c9095c2f1126d7db785955c73cdc5fc33e7c3fa911bd4a42931672029a7" checksum = "0ae95c9095c2f1126d7db785955c73cdc5fc33e7c3fa911bd4a42931672029a7"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"impl-tools-lib", "impl-tools-lib 0.11.4",
"proc-macro-error2", "proc-macro-error2",
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "impl-tools"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c8567c75b167cdda5ecee024da9cc9581d41bd52460e2e0e768967972aeef1"
dependencies = [
"autocfg",
"impl-tools-lib 0.12.0",
"proc-macro-error3",
"syn 2.0.117",
]
[[package]] [[package]]
name = "impl-tools-lib" name = "impl-tools-lib"
version = "0.11.4" version = "0.11.4"
@@ -1100,6 +1123,18 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "impl-tools-lib"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c81eb99ba7c6145aeccb8857ddc0c611cd52440091dcfddd605b6a930c9afbf4"
dependencies = [
"proc-macro-error3",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.14.0" version = "2.14.0"
@@ -1519,7 +1554,7 @@ dependencies = [
name = "obicompactvec" name = "obicompactvec"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"common_traits", "common_traits 0.13.0",
"memmap2", "memmap2",
"ndarray", "ndarray",
"rayon", "rayon",
@@ -2009,6 +2044,16 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "proc-macro-error-attr3"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0084e6206a967a2dad822180626b2f6b07a3b379325e8f1ec0438e33a469ba7"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "proc-macro-error2" name = "proc-macro-error2"
version = "2.0.1" version = "2.0.1"
@@ -2020,6 +2065,17 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "proc-macro-error3"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cf066225f2373bc711684792b69bdeac0356019b007e721090c24d92d5d5a50"
dependencies = [
"proc-macro-error-attr3",
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -2093,7 +2149,7 @@ dependencies = [
"cacheline-ef", "cacheline-ef",
"clap", "clap",
"colored", "colored",
"common_traits", "common_traits 0.11.4",
"epserde", "epserde",
"epserde-derive", "epserde-derive",
"fastrand", "fastrand",
+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
common_traits = "0.11" common_traits = "0.13"
memmap2 = "0.9" memmap2 = "0.9"
ndarray = "0.17" ndarray = "0.17"
rayon = "1" rayon = "1"
@@ -200,7 +200,7 @@ impl PersistentBitMatrix {
/// buffer, ever — the whole point, see /// buffer, ever — the whole point, see
/// `PersistentSparseBitMatrix::nonzero_iter`); `Columnar`/`Packed` /// `PersistentSparseBitMatrix::nonzero_iter`); `Columnar`/`Packed`
/// reuse the same sorted-slot batching `fill_sub_matrix` always used /// reuse the same sorted-slot batching `fill_sub_matrix` always used
/// (`BitSliceView::nonzero_among_sorted`), just eagerly collected here /// (`BitSliceView::enumerate_nonzero_slots`), just eagerly collected here
/// rather than filled into a dense buffer — same `get` calls, same /// rather than filled into a dense buffer — same `get` calls, same
/// mmap locality, no per-format duplication of that logic elsewhere. /// mmap locality, no per-format duplication of that logic elsewhere.
/// `Implicit` is trivial (one column, always present). /// `Implicit` is trivial (one column, always present).
@@ -228,7 +228,7 @@ impl PersistentBitMatrix {
for c in 0..n_cols { for c in 0..n_cols {
let view = self.col_view(c); let view = self.col_view(c);
hits.extend( hits.extend(
view.nonzero_among_sorted(&sorted_slots) view.enumerate_nonzero_slots(&sorted_slots)
.map(|pos| (perm[pos], c, 1u32)), .map(|pos| (perm[pos], c, 1u32)),
); );
} }
+7 -28
View File
@@ -75,36 +75,15 @@ impl PersistentBitVec {
/// The slots are sorted internally before reading so that accesses to /// The slots are sorted internally before reading so that accesses to
/// the underlying mmap are as sequential as possible, then the results /// the underlying mmap are as sequential as possible, then the results
/// are reordered to match the input order. /// are reordered to match the input order.
pub fn get_batch(&self, slots: &[usize]) -> Vec<bool> { #[inline]
let mut out = vec![false; slots.len()]; pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<bool> {
self.fill_batch(slots, &mut out); self.view().collect_slots_values(slots)
out
} }
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) { #[inline]
assert_eq!(slots.len(), out.len()); pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) {
let n = slots.len(); self.view().fill_slots_values(slots, out)
if n == 0 {
return;
}
let mut perm: Vec<usize> = (0..n).collect();
perm.sort_by_key(|&i| slots[i]);
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
let mut tmp = vec![false; n];
self.fill_batch_sorted(&sorted, &mut tmp);
for (i, &orig_idx) in perm.iter().enumerate() {
out[orig_idx] = tmp[i];
}
}
/// Fill `out` assuming `sorted_slots` is already in ascending order.
/// Results are written in `sorted_slots` order (no reordering).
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) {
assert_eq!(sorted_slots.len(), out.len());
for (i, &slot) in sorted_slots.iter().enumerate() {
out[i] = self.get(slot);
}
} }
// SAFETY: mmap is page-aligned, HEADER_SIZE=16 divisible by 8 → u64-aligned. // SAFETY: mmap is page-aligned, HEADER_SIZE=16 divisible by 8 → u64-aligned.
+4 -4
View File
@@ -372,7 +372,7 @@ impl PersistentCompactIntMatrix {
let mut out: Vec<Vec<u32>> = Vec::with_capacity(n_cols); let mut out: Vec<Vec<u32>> = Vec::with_capacity(n_cols);
for c in 0..n_cols { for c in 0..n_cols {
let mut col_buf = vec![0u32; slots.len()]; let mut col_buf = vec![0u32; slots.len()];
self.col_view(c).fill_batch(slots, &mut col_buf); self.col_view(c).fill_slots_values(slots, &mut col_buf);
out.push(col_buf); out.push(col_buf);
} }
out out
@@ -397,7 +397,7 @@ impl PersistentCompactIntMatrix {
for (c, col) in out.iter_mut().enumerate() { for (c, col) in out.iter_mut().enumerate() {
col.resize(n, 0); col.resize(n, 0);
let mut tmp = vec![0u32; n]; let mut tmp = vec![0u32; n];
self.col_view(c).fill_batch_sorted(&sorted_slots, &mut tmp); self.col_view(c).fill_slots_values_sorted(&sorted_slots, &mut tmp);
for (i, &orig_idx) in perm.iter().enumerate() { for (i, &orig_idx) in perm.iter().enumerate() {
col[orig_idx] = tmp[i]; col[orig_idx] = tmp[i];
} }
@@ -411,7 +411,7 @@ impl PersistentCompactIntMatrix {
/// design even though no sparse count format exists yet). No native /// design even though no sparse count format exists yet). No native
/// low-effort case here the way `PersistentSparseBitMatrix` has one — /// low-effort case here the way `PersistentSparseBitMatrix` has one —
/// both variants reuse the same sorted-slot batching `fill_sub_matrix` /// both variants reuse the same sorted-slot batching `fill_sub_matrix`
/// already used, via `IntSliceView::nonzero_among_sorted`. /// already used, via `IntSliceView::enumerate_nonzero_slots`.
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator<Item = (usize, usize, u32)> + 'a { pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator<Item = (usize, usize, u32)> + 'a {
let n = slots.len(); let n = slots.len();
let mut perm: Vec<usize> = (0..n).collect(); let mut perm: Vec<usize> = (0..n).collect();
@@ -422,7 +422,7 @@ impl PersistentCompactIntMatrix {
for c in 0..n_cols { for c in 0..n_cols {
hits.extend( hits.extend(
self.col_view(c) self.col_view(c)
.nonzero_among_sorted(&sorted_slots) .enumerate_nonzero_slots(&sorted_slots)
.map(|(pos, v)| (perm[pos], c, v)), .map(|(pos, v)| (perm[pos], c, v)),
); );
} }
+7 -26
View File
@@ -67,34 +67,15 @@ impl PersistentCompactIntVec {
/// The slots are sorted internally before reading so that accesses to /// The slots are sorted internally before reading so that accesses to
/// the underlying mmap are as sequential as possible, then the results /// the underlying mmap are as sequential as possible, then the results
/// are reordered to match the input order. /// are reordered to match the input order.
pub fn get_batch(&self, slots: &[usize]) -> Vec<u32> { #[inline]
let mut out = vec![0u32; slots.len()]; pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<u32> {
self.fill_batch(slots, &mut out); self.view().collect_slots_values(slots)
out
} }
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) { #[inline]
assert_eq!(slots.len(), out.len()); pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) {
let n = slots.len(); self.view().fill_slots_values(slots, out)
if n == 0 { return; }
let mut perm: Vec<usize> = (0..n).collect();
perm.sort_by_key(|&i| slots[i]);
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
let mut tmp = vec![0u32; n];
self.fill_batch_sorted(&sorted, &mut tmp);
for (i, &orig_idx) in perm.iter().enumerate() {
out[orig_idx] = tmp[i];
}
}
/// Fill `out` assuming `sorted_slots` is already in ascending order.
/// Results are written in `sorted_slots` order (no reordering).
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) {
assert_eq!(sorted_slots.len(), out.len());
for (i, &slot) in sorted_slots.iter().enumerate() {
out[i] = self.get(slot);
}
} }
fn overflow_get(&self, slot: usize) -> u32 { fn overflow_get(&self, slot: usize) -> u32 {
+18 -18
View File
@@ -215,65 +215,65 @@ fn hamming_dist_basic() {
assert_eq!(ra.hamming_dist(&rb), 2); assert_eq!(ra.hamming_dist(&rb), 2);
} }
// ── get_batch tests ──────────────────────────────────────────────────────────── // ── collect_slots_values tests ────────────────────────────────────────────────────────────
#[test] #[test]
fn bitvec_get_batch_in_order() { fn bitvec_collect_slots_values_in_order() {
let bits = vec![true, false, true, false, true]; let bits = vec![true, false, true, false, true];
let (_dir, r) = make_bv(&bits); let (_dir, r) = make_bv(&bits);
let got = r.get_batch(&[0, 1, 2, 3, 4]); let got = r.collect_slots_values(&[0, 1, 2, 3, 4]);
assert_eq!(got, bits); assert_eq!(got, bits);
} }
#[test] #[test]
fn bitvec_get_batch_out_of_order() { fn bitvec_collect_slots_values_out_of_order() {
let bits = vec![true, false, true, false, true]; let bits = vec![true, false, true, false, true];
let (_dir, r) = make_bv(&bits); let (_dir, r) = make_bv(&bits);
let got = r.get_batch(&[3, 0, 4, 1]); let got = r.collect_slots_values(&[3, 0, 4, 1]);
assert_eq!(got, vec![false, true, true, false]); assert_eq!(got, vec![false, true, true, false]);
} }
#[test] #[test]
fn bitvec_get_batch_with_duplicates() { fn bitvec_collect_slots_values_with_duplicates() {
let bits = vec![true, false, true]; let bits = vec![true, false, true];
let (_dir, r) = make_bv(&bits); let (_dir, r) = make_bv(&bits);
let got = r.get_batch(&[0, 2, 0, 1]); let got = r.collect_slots_values(&[0, 2, 0, 1]);
assert_eq!(got, vec![true, true, true, false]); assert_eq!(got, vec![true, true, true, false]);
} }
#[test] #[test]
fn bitvec_get_batch_empty() { fn bitvec_collect_slots_values_empty() {
let (_dir, r) = make_bv(&[true, false]); let (_dir, r) = make_bv(&[true, false]);
let got = r.get_batch(&[]); let got = r.collect_slots_values(&[]);
assert!(got.is_empty()); assert!(got.is_empty());
} }
#[test] #[test]
fn bitvec_get_batch_out_of_bounds_returns_false() { fn bitvec_collect_slots_values_out_of_bounds_returns_false() {
// PersistentBitVec::get does NOT bounds-check — out-of-range slots read // PersistentBitVec::get does NOT bounds-check — out-of-range slots read
// whatever bit happens to be in the mmap (zero-initialised, so false). // whatever bit happens to be in the mmap (zero-initialised, so false).
// This is a known gap; get_batch inherits it. // This is a known gap; collect_slots_values inherits it.
let (_dir, r) = make_bv(&[true, false]); let (_dir, r) = make_bv(&[true, false]);
let got = r.get_batch(&[0, 2]); let got = r.collect_slots_values(&[0, 2]);
assert_eq!(got, vec![true, false]); assert_eq!(got, vec![true, false]);
} }
// BitSliceView get_batch (same logic, exercised through the view) // BitSliceView collect_slots_values (same logic, exercised through the view)
#[test] #[test]
fn bitslice_view_get_batch() { fn bitslice_view_collect_slots_values() {
let bits = vec![true, false, true, false, true]; let bits = vec![true, false, true, false, true];
let (_dir, r) = make_bv(&bits); let (_dir, r) = make_bv(&bits);
let view = r.view(); let view = r.view();
assert_eq!(view.get_batch(&[0, 1, 2]), vec![true, false, true]); assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![true, false, true]);
assert_eq!(view.get_batch(&[4, 3]), vec![true, false]); assert_eq!(view.collect_slots_values(&[4, 3]), vec![true, false]);
} }
#[test] #[test]
fn bitslice_view_get_batch_out_of_bounds_returns_false() { fn bitslice_view_collect_slots_values_out_of_bounds_returns_false() {
// BitSliceView::get does NOT bounds-check either. // BitSliceView::get does NOT bounds-check either.
let bits = vec![true, false]; let bits = vec![true, false];
let (_dir, r) = make_bv(&bits); let (_dir, r) = make_bv(&bits);
let view = r.view(); let view = r.view();
let got = view.get_batch(&[0, 2]); let got = view.collect_slots_values(&[0, 2]);
assert_eq!(got, vec![true, false]); assert_eq!(got, vec![true, false]);
} }
+16 -16
View File
@@ -400,7 +400,7 @@ fn partial_relfreq_bray_additive_across_split() {
} }
} }
// ── get_batch tests ──────────────────────────────────────────────────────────── // ── collect_slots_values tests ────────────────────────────────────────────────────────────
fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) { fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
@@ -413,49 +413,49 @@ fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
} }
#[test] #[test]
fn pciv_get_batch_in_order() { fn pciv_collect_slots_values_in_order() {
let counts = vec![10u32, 255, 300, 1000]; let counts = vec![10u32, 255, 300, 1000];
let (_dir, v) = make_pciv(&counts); let (_dir, v) = make_pciv(&counts);
let got = v.get_batch(&[0, 1, 2, 3]); let got = v.collect_slots_values(&[0, 1, 2, 3]);
assert_eq!(got, counts); assert_eq!(got, counts);
} }
#[test] #[test]
fn pciv_get_batch_out_of_order() { fn pciv_collect_slots_values_out_of_order() {
let counts = vec![10u32, 255, 300, 1000]; let counts = vec![10u32, 255, 300, 1000];
let (_dir, v) = make_pciv(&counts); let (_dir, v) = make_pciv(&counts);
let got = v.get_batch(&[3, 0, 2, 1]); let got = v.collect_slots_values(&[3, 0, 2, 1]);
assert_eq!(got, vec![1000, 10, 300, 255]); assert_eq!(got, vec![1000, 10, 300, 255]);
} }
#[test] #[test]
fn pciv_get_batch_with_duplicates() { fn pciv_collect_slots_values_with_duplicates() {
let counts = vec![10u32, 255, 300]; let counts = vec![10u32, 255, 300];
let (_dir, v) = make_pciv(&counts); let (_dir, v) = make_pciv(&counts);
let got = v.get_batch(&[0, 2, 0, 1]); let got = v.collect_slots_values(&[0, 2, 0, 1]);
assert_eq!(got, vec![10, 300, 10, 255]); assert_eq!(got, vec![10, 300, 10, 255]);
} }
#[test] #[test]
fn pciv_get_batch_empty() { fn pciv_collect_slots_values_empty() {
let (_dir, v) = make_pciv(&[10u32, 20]); let (_dir, v) = make_pciv(&[10u32, 20]);
let got: Vec<u32> = v.get_batch(&[]); let got: Vec<u32> = v.collect_slots_values(&[]);
assert!(got.is_empty()); assert!(got.is_empty());
} }
#[test] #[test]
fn pciv_get_batch_out_of_bounds_panics() { fn pciv_collect_slots_values_out_of_bounds_panics() {
let (_dir, v) = make_pciv(&[10u32, 20]); let (_dir, v) = make_pciv(&[10u32, 20]);
let result = std::panic::catch_unwind(|| v.get_batch(&[0, 2])); let result = std::panic::catch_unwind(|| v.collect_slots_values(&[0, 2]));
assert!(result.is_err(), "get_batch should panic on out-of-bounds slot"); assert!(result.is_err(), "collect_slots_values should panic on out-of-bounds slot");
} }
// IntSliceView get_batch (same logic, exercised through the view) // IntSliceView collect_slots_values (same logic, exercised through the view)
#[test] #[test]
fn intslice_view_get_batch() { fn intslice_view_collect_slots_values() {
let counts = vec![10u32, 255, 300, 1000]; let counts = vec![10u32, 255, 300, 1000];
let (_dir, v) = make_pciv(&counts); let (_dir, v) = make_pciv(&counts);
let view = v.view(); let view = v.view();
assert_eq!(view.get_batch(&[0, 1, 2]), vec![10, 255, 300]); assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![10, 255, 300]);
assert_eq!(view.get_batch(&[3, 1]), vec![1000, 255]); assert_eq!(view.collect_slots_values(&[3, 1]), vec![1000, 255]);
} }
+63 -44
View File
@@ -30,13 +30,13 @@ impl<'a> BitSliceView<'a> {
/// ///
/// Slots are sorted internally before reading, then results are reordered /// Slots are sorted internally before reading, then results are reordered
/// to match the input order. /// to match the input order.
pub fn get_batch(&self, slots: &[usize]) -> Vec<bool> { pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<bool> {
let mut out = vec![false; slots.len()]; let mut out = vec![false; slots.len()];
self.fill_batch(slots, &mut out); self.fill_slots_values(slots, &mut out);
out out
} }
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) { pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) {
assert_eq!(slots.len(), out.len()); assert_eq!(slots.len(), out.len());
let n = slots.len(); let n = slots.len();
if n == 0 { return; } if n == 0 { return; }
@@ -44,32 +44,43 @@ impl<'a> BitSliceView<'a> {
perm.sort_by_key(|&i| slots[i]); perm.sort_by_key(|&i| slots[i]);
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect(); let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
let mut tmp = vec![false; n]; let mut tmp = vec![false; n];
self.fill_batch_sorted(&sorted, &mut tmp); self.fill_slots_values_sorted(&sorted, &mut tmp);
for (i, &orig_idx) in perm.iter().enumerate() { for (i, &orig_idx) in perm.iter().enumerate() {
out[orig_idx] = tmp[i]; out[orig_idx] = tmp[i];
} }
} }
/// Fill `out` assuming `sorted_slots` is already in ascending order. /// `(position in slots, value)` for every slot. Plain `enumerate` +
/// Results are written in `sorted_slots` order (no reordering). /// `get`: order never affects correctness here, only the caller's cache
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) { /// locality (see `enumerate_nonzero_slots`), so this takes `slots` as
assert_eq!(sorted_slots.len(), out.len()); /// given.
for (i, &slot) in sorted_slots.iter().enumerate() { #[inline]
out[i] = self.get(slot); pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, bool)> + 'b
}
}
/// Positions in `sorted_slots` (not the slots themselves) whose bit is
/// set — a `filter` over `get`, not a new traversal. `sorted_slots`
/// need not actually be sorted for correctness, but callers batching
/// several columns over the same slot set sort once and reuse it for
/// cache-friendlier `get` access (see `PersistentBitMatrix::nonzero_iter`).
pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator<Item = usize> + 'b
where where
Self: 'b, Self: 'b,
{ {
sorted_slots.iter().enumerate() slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot)))
.filter_map(move |(pos, &slot)| self.get(slot).then_some(pos)) }
/// Fill `out` assuming `sorted_slots` is already in ascending order.
/// Results are written in `sorted_slots` order (no reordering).
pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) {
assert_eq!(sorted_slots.len(), out.len());
for (pos, v) in self.enumerate_slots_values(sorted_slots) {
out[pos] = v;
}
}
/// Positions in `slots` (not the slots themselves) whose bit is set.
/// Order of `slots` never matters for correctness; callers batching
/// several columns over the same slot set sort it once beforehand
/// purely for cache-friendlier `get` access (see
/// `PersistentBitMatrix::nonzero_iter`).
pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = usize> + 'b
where
Self: 'b,
{
self.enumerate_slots_values(slots).filter_map(|(pos, v)| v.then_some(pos))
} }
pub fn count_ones(&self) -> u64 { pub fn count_ones(&self) -> u64 {
@@ -184,14 +195,14 @@ impl<'a> IntSliceView<'a> {
/// ///
/// Slots are sorted internally before reading, then results are reordered /// Slots are sorted internally before reading, then results are reordered
/// to match the input order. /// to match the input order.
pub fn get_batch(&self, slots: &[usize]) -> Vec<u32> { pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<u32> {
let mut out = vec![0u32; slots.len()]; let mut out = vec![0u32; slots.len()];
self.fill_batch(slots, &mut out); self.fill_slots_values(slots, &mut out);
out out
} }
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) { pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) {
assert_eq!(slots.len(), out.len()); assert_eq!(slots.len(), out.len());
let n = slots.len(); let n = slots.len();
if n == 0 { return; } if n == 0 { return; }
@@ -199,32 +210,40 @@ impl<'a> IntSliceView<'a> {
perm.sort_by_key(|&i| slots[i]); perm.sort_by_key(|&i| slots[i]);
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect(); let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
let mut tmp = vec![0u32; n]; let mut tmp = vec![0u32; n];
self.fill_batch_sorted(&sorted, &mut tmp); self.fill_slots_values_sorted(&sorted, &mut tmp);
for (i, &orig_idx) in perm.iter().enumerate() { for (i, &orig_idx) in perm.iter().enumerate() {
out[orig_idx] = tmp[i]; out[orig_idx] = tmp[i];
} }
} }
/// Fill `out` assuming `sorted_slots` is already in ascending order. /// `(position in slots, value)` for every slot. See
/// Results are written in `sorted_slots` order (no reordering). /// [`BitSliceView::enumerate_slots_values`]: order never affects
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) { /// correctness here.
assert_eq!(sorted_slots.len(), out.len()); #[inline]
for (i, &slot) in sorted_slots.iter().enumerate() { pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
out[i] = self.get(slot);
}
}
/// Like [`BitSliceView::nonzero_among_sorted`]: `(position in
/// sorted_slots, value)` for every nonzero value — a `filter_map` over
/// `get`, not a new traversal.
pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
where where
Self: 'b, Self: 'b,
{ {
sorted_slots.iter().enumerate().filter_map(move |(pos, &slot)| { slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot)))
let v = self.get(slot); }
(v != 0).then_some((pos, v))
}) /// Fill `out` assuming `sorted_slots` is already in ascending order.
/// Results are written in `sorted_slots` order (no reordering).
pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) {
assert_eq!(sorted_slots.len(), out.len());
for (pos, v) in self.enumerate_slots_values(sorted_slots) {
out[pos] = v;
}
}
/// Like [`BitSliceView::enumerate_nonzero_slots`]: `(position in slots,
/// value)` for every nonzero value — a `filter_map` over `get`, not a
/// new traversal.
pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
where
Self: 'b,
{
self.enumerate_slots_values(slots).filter_map(|(pos, v)| (v != 0).then_some((pos, v)))
} }
/// Sequential merge scan: yields all n values in slot order. /// Sequential merge scan: yields all n values in slot order.