Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions crates/dr256-ternlog-lab/examples/selection_forms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Measures three forms of the same slab planes: the contiguous words the
//! fold reads, the borrowed per-row `Selection` view, and one owned
//! `WideFieldMask` per row (the measured alternative, built here only).
//! Every arm's answer is checked against a row-at-a-time oracle first.

use dr256_ternlog_lab::*;
use lance_graph_contract::class_view::WideFieldMask;
use lance_graph_dt64::dr::Scope;
use lance_graph_mask_risc::LaneRef;
use std::hint::black_box;
use std::time::Instant;

fn mix(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^ (x >> 31)
}
/// 3 of 4 rows are present.
fn present(i: usize) -> bool {
!mix(i as u64).is_multiple_of(4)
}
/// 7 of 8 rows are in the reader's scope.
fn in_scope(i: usize) -> bool {
!mix(i as u64 ^ 0xA5A5).is_multiple_of(8)
}

// One heap allocation per row: position 255 forces the four-chunk form.
fn owned(sel: Selection<'_>) -> WideFieldMask {
let mut m = WideFieldMask::EMPTY.with(255);
let w = m.words_mut();
w.fill(0);
w[..sel.words().len()].copy_from_slice(sel.words());
m
}

fn fmt(ns: f64) -> String {
if ns < 10_000.0 {
format!("{ns:.0}ns")
} else {
format!("{:.2}\u{b5}s", ns / 1_000.0)
}
}

/// Median and minimum of 31 samples, each `k` calls under one clock read.
fn measure(n: usize, arm: &str, k: u32, want: u64, f: &mut dyn FnMut() -> u64) -> f64 {
assert_eq!(f(), want, "arm {arm}");
for _ in 0..5 {
black_box(f());
}
let mut samples = Vec::with_capacity(31);
for _ in 0..31 {
let t = Instant::now();
for _ in 0..k {
black_box(f());
}
samples.push(t.elapsed().as_nanos() as f64 / f64::from(k));
}
samples.sort_by(|a, b| a.partial_cmp(b).unwrap());
println!(
"n={n} arm={arm} median={} min={}",
fmt(samples[15]),
fmt(samples[0])
);
samples[15]
}

fn sum3(a: &[u64], b: &[u64], c: &[u64]) -> u64 {
a.iter()
.zip(b)
.zip(c)
.map(|((a, b), c)| u64::from((a & b & c).count_ones()))
.sum()
}

fn main() {
let world = World::new();
for n in [65_536usize, 1_000_000] {
let k = if n == 65_536 { 200 } else { 20 };
let id: Vec<u32> = (0..n as u32).collect();
let age: Vec<i32> = (0..n as i32).map(|i| 20 + (i % 60)).collect();
let table = Table::new(
PERSON,
1,
n,
1,
Scope { id: 7 },
&[
(person::ID, LaneRef::U32(&id)),
(person::AGE, LaneRef::I32(&age)),
],
);
let mask = select(&world.view, PERSON, &["person_id", "age"]).unwrap();
let prepared = prepare_nodes(&world, &table, &mask, present, in_scope).unwrap();
let want = (0..n).filter(|&i| present(i) && in_scope(i)).count() as u64;
let slabs = prepared.slabs();

// The owned form, built once here; building it again is the build arm.
let build = || -> Vec<[Vec<WideFieldMask>; 3]> {
(0..slabs)
.map(|s| {
let one = |p: usize| prepared.plane(s, p).selections().map(owned).collect();
[one(0), one(1), one(2)]
})
.collect()
};
let owned_form = build();
let values: usize = owned_form
.iter()
.map(|planes| planes.iter().map(Vec::len).sum::<usize>())
.sum();
let selections: usize = (0..slabs)
.flat_map(|s| (0..3).map(move |p| (s, p)))
.map(|(s, p)| prepared.plane(s, p).masks())
.sum();
assert_eq!(values, selections);
println!(
"n={n} slabs={slabs} selections={selections} contiguous_bytes={} owned_allocations={values} owned_bytes={}",
prepared.plane_bytes(),
values * (std::mem::size_of::<WideFieldMask>() + 32)
);

let mut fold = Fold::count_and3().unwrap();
measure(n, "fold/contiguous", k, want, &mut || {
fold.count(&world, &table, &prepared).unwrap()
});

let contig = measure(n, "words/contiguous", k, want, &mut || {
(0..slabs)
.map(|s| {
sum3(
&prepared.plane(s, 0).words,
&prepared.plane(s, 1).words,
&prepared.plane(s, 2).words,
)
})
.sum()
});

let view = measure(n, "words/borrowed_view", k, want, &mut || {
let mut total = 0u64;
for s in 0..slabs {
let (a, b, c) = (
prepared.plane(s, 0),
prepared.plane(s, 1),
prepared.plane(s, 2),
);
for y in 0..a.masks() {
let (x, y1, z) = (a.mask(y).unwrap(), b.mask(y).unwrap(), c.mask(y).unwrap());
total += sum3(x.words(), y1.words(), z.words());
}
}
total
});

let wide = measure(n, "words/owned_wide", k, want, &mut || {
let mut total = 0u64;
for planes in &owned_form {
for ((x, y1), z) in planes[0].iter().zip(&planes[1]).zip(&planes[2]) {
total += sum3(x.words(), y1.words(), z.words());
}
}
total
});

// Three reusable contiguous buffers per slab, allocated outside the timing.
let mut buffers: Vec<[Vec<u64>; 3]> = (0..slabs)
.map(|s| {
let len = prepared.plane(s, 0).words.len();
[vec![0; len], vec![0; len], vec![0; len]]
})
.collect();
let gather = measure(n, "gather/owned_wide", k, want, &mut || {
for (planes, bufs) in owned_form.iter().zip(buffers.iter_mut()) {
for p in 0..3 {
let len = bufs[p].len();
for (y, m) in planes[p].iter().enumerate() {
let lo = y * MASK_WORDS;
let take = MASK_WORDS.min(len - lo);
bufs[p][lo..lo + take].copy_from_slice(&m.words()[..take]);
}
}
}
buffers.iter().map(|b| sum3(&b[0], &b[1], &b[2])).sum()
});

measure(n, "build/owned_wide", 1, values as u64, &mut || {
build()
.iter()
.map(|planes| planes.iter().map(Vec::len).sum::<usize>() as u64)
.sum()
});

println!(
"n={n} ratios borrowed_view/contiguous={:.2} owned_wide/contiguous={:.2} gather/contiguous={:.2}",
view / contig,
wide / contig,
gather / contig
);
}
}
91 changes: 90 additions & 1 deletion crates/dr256-ternlog-lab/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! ```text
//! DT64 ClassView (the menu) what a field means, in which order
//! DR WideFieldMask (the selection) which fields: storage, compute, display
//! Plane (one per slab) which rows: presence, selection, scope
//! Plane (one per slab) which rows: 256 selections of 256, borrowed
//! RankMap (global -> local) where a global id sits after compaction
//! exec mask-RISC ternlog(a, b, c) -> popcount, per slab
//! ```
Expand Down Expand Up @@ -50,6 +50,12 @@ pub const SLAB_WORDS: usize = SLAB_ROWS / 64;
pub const MAX_SLABS: usize = 256;
/// `a & b & c` in the VPTERNLOG index convention.
pub const AND3: u8 = 0x80;
/// Positions of one selection: one row of a slab.
pub const MASK_BITS: usize = 256;
/// Words of one selection, the chunk count of a 256-position `WideFieldMask`.
pub const MASK_WORDS: usize = MASK_BITS / 64;
/// Selections a full slab plane holds.
pub const SLAB_MASKS: usize = SLAB_ROWS / MASK_BITS;

// ------------------------------------------------------------------ DT64

Expand Down Expand Up @@ -357,6 +363,7 @@ impl LaneSource for SlabSource<'_, '_> {
// ---------------------------------------------------------------- planes

/// One mask plane over one slab, with the row order it is in.
/// Read as 256 selections of 256 positions through [`Plane::mask`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plane {
pub space: CoordSpace,
Expand All @@ -378,6 +385,88 @@ impl Plane {
pub fn count(&self) -> u64 {
self.words.iter().map(|w| u64::from(w.count_ones())).sum()
}

/// Selections this plane holds: one per 256 positions, the last one short
/// when the slab is.
pub fn masks(&self) -> usize {
self.words.len().div_ceil(MASK_WORDS)
}

/// Selection `row` of this plane, borrowed in place. `None` past the plane
/// or past row 255.
pub fn mask(&self, row: usize) -> Option<Selection<'_>> {
let at = u8::try_from(row).ok()?;
let lo = row * MASK_WORDS;
if lo >= self.words.len() {
return None;
}
let hi = (lo + MASK_WORDS).min(self.words.len());
Some(Selection {
space: self.space,
row: at,
words: &self.words[lo..hi],
})
}

/// Every selection, in row order.
pub fn selections(&self) -> impl Iterator<Item = Selection<'_>> + '_ {
(0..self.masks()).filter_map(|row| self.mask(row))
}
}

/// One reusable selection of a slab plane: 256 positions, borrowed from the
/// plane's own words, with the row order it is in. The words are the chunks
/// of a `WideFieldMask` over the same positions (chunk `k` = positions
/// `64k..64k+63`); nothing is copied to read them.
///
/// A plane's last selection is short when the slab is: fewer than four
/// words, and every position past the slab's rows reads absent.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection<'p> {
space: CoordSpace,
row: u8,
words: &'p [u64],
}

impl<'p> Selection<'p> {
/// The row order the selection is in: its plane's.
pub fn space(&self) -> CoordSpace {
self.space
}

/// Which of the plane's selections this is.
pub fn row(&self) -> u8 {
self.row
}

/// The chunks, borrowed from the plane. At most [`MASK_WORDS`].
pub fn words(&self) -> &'p [u64] {
self.words
}

/// Is position `n` selected?
pub fn has(&self, n: u8) -> bool {
self.words
.get(usize::from(n) / 64)
.is_some_and(|w| w >> (n % 64) & 1 == 1)
}

/// Selected positions.
pub fn count(&self) -> u32 {
self.words.iter().map(|w| w.count_ones()).sum()
}

pub fn is_empty(&self) -> bool {
self.words.iter().all(|&w| w == 0)
}

/// Whether `mask` selects exactly these positions, whatever width either
/// side stores. Reads both in place.
pub fn same_bits(&self, mask: &WideFieldMask) -> bool {
let other = mask.words();
(0..self.words.len().max(other.len()))
.all(|i| self.words.get(i).copied().unwrap_or(0) == other.get(i).copied().unwrap_or(0))
}
}

/// Global to local as a mask: a global id's local row is its rank among the
Expand Down
Loading
Loading