|
| 1 | +//! Cost split of the HDR rolling floor: the per-observation hot path versus |
| 2 | +//! the periodic shape path. |
| 3 | +//! |
| 4 | +//! ```sh |
| 5 | +//! cargo run --release --example hdr_rolling_floor_bench |
| 6 | +//! ``` |
| 7 | +//! |
| 8 | +//! Hot path, per observation: |
| 9 | +//! 1. popcount / Hamming distance of two 2048-byte vectors |
| 10 | +//! 2. exact moments update (`MomentsU32::observe`, and `moments_u32` batch) |
| 11 | +//! 3. reservoir update |
| 12 | +//! 4. full rolling-floor update (moments + reservoir + checkpoint test) |
| 13 | +//! |
| 14 | +//! Periodic path, once per 1000 observations: |
| 15 | +//! 5. shape evaluation (sort 1000 samples, median, kurtosis) |
| 16 | +//! 6. empirical shape: locate 8 σ-lattice levels |
| 17 | +//! |
| 18 | +//! Query path, on demand (nothing is stored): |
| 19 | +//! 7. Gaussian thresholds of 8 levels |
| 20 | +//! 8. shade of a response over 8 levels |
| 21 | +
|
| 22 | +use ndarray::hpc::bitwise::hamming_distance_raw; |
| 23 | +use ndarray::hpc::rolling_floor::{quantile_of_sorted, EmpiricalShape, ReservoirU32, RollingFloor, SigmaLevel}; |
| 24 | +use ndarray::hpc::statistics::{moments_u32, MomentsU32}; |
| 25 | +use std::hint::black_box; |
| 26 | +use std::time::Instant; |
| 27 | + |
| 28 | +fn xorshift(n: usize, mut s: u64) -> Vec<u64> { |
| 29 | + (0..n) |
| 30 | + .map(|_| { |
| 31 | + s ^= s << 13; |
| 32 | + s ^= s >> 7; |
| 33 | + s ^= s << 17; |
| 34 | + s |
| 35 | + }) |
| 36 | + .collect() |
| 37 | +} |
| 38 | + |
| 39 | +fn ns_per(label: &str, n: usize, f: impl FnOnce()) { |
| 40 | + let t = Instant::now(); |
| 41 | + f(); |
| 42 | + let dt = t.elapsed(); |
| 43 | + println!("{label:<44} {:>9.2} ns/op ({n} ops)", dt.as_nanos() as f64 / n as f64); |
| 44 | +} |
| 45 | + |
| 46 | +fn main() { |
| 47 | + println!("avx512f={} avx2={}", cfg!(target_feature = "avx512f"), cfg!(target_feature = "avx2")); |
| 48 | + |
| 49 | + const VBYTES: usize = 2048; // 16384-bit vectors |
| 50 | + const N: usize = 2_000_000; |
| 51 | + let words = xorshift(VBYTES / 8 * 65, 1); |
| 52 | + let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect(); |
| 53 | + let query = &bytes[..VBYTES]; |
| 54 | + let db = &bytes[VBYTES..]; |
| 55 | + let dists: Vec<u32> = (0..N) |
| 56 | + .map(|i| { |
| 57 | + let j = i % 64; |
| 58 | + hamming_distance_raw(query, &db[j * VBYTES..(j + 1) * VBYTES]) as u32 |
| 59 | + }) |
| 60 | + .collect(); |
| 61 | + |
| 62 | + println!("-- hot path, per observation --"); |
| 63 | + ns_per("1. popcount/Hamming (2048 B)", N, || { |
| 64 | + let mut acc = 0u64; |
| 65 | + for i in 0..N { |
| 66 | + let j = i % 64; |
| 67 | + acc += hamming_distance_raw(black_box(query), &db[j * VBYTES..(j + 1) * VBYTES]); |
| 68 | + } |
| 69 | + black_box(acc); |
| 70 | + }); |
| 71 | + ns_per("2a. MomentsU32::observe (scalar)", N, || { |
| 72 | + let mut m = MomentsU32::default(); |
| 73 | + for &d in &dists { |
| 74 | + m.observe(black_box(d)); |
| 75 | + } |
| 76 | + black_box(m); |
| 77 | + }); |
| 78 | + ns_per("2b. moments_u32 (batch)", N, || { |
| 79 | + black_box(moments_u32(black_box(&dists))); |
| 80 | + }); |
| 81 | + ns_per("3. ReservoirU32::observe (cap 1000)", N, || { |
| 82 | + let mut r = ReservoirU32::new(1000); |
| 83 | + for &d in &dists { |
| 84 | + r.observe(black_box(d)); |
| 85 | + } |
| 86 | + black_box(r.len()); |
| 87 | + }); |
| 88 | + ns_per("4a. RollingFloor::observe (incl. checkpoints)", N, || { |
| 89 | + let mut f = RollingFloor::for_width(16384); |
| 90 | + for &d in &dists { |
| 91 | + if let Some(s) = f.observe(black_box(d)) { |
| 92 | + f.recalibrate(&s); |
| 93 | + } |
| 94 | + } |
| 95 | + black_box(f.mu()); |
| 96 | + }); |
| 97 | + ns_per("4b. RollingFloor::observe_batch (incl. checkpoints)", N, || { |
| 98 | + let mut f = RollingFloor::for_width(16384); |
| 99 | + let mut rest: &[u32] = &dists; |
| 100 | + while !rest.is_empty() { |
| 101 | + let (used, s) = f.observe_batch(rest); |
| 102 | + rest = &rest[used..]; |
| 103 | + if let Some(s) = s { |
| 104 | + f.recalibrate(&s); |
| 105 | + } |
| 106 | + } |
| 107 | + black_box(f.mu()); |
| 108 | + }); |
| 109 | + |
| 110 | + println!("-- periodic path, per checkpoint (every 1000 observations) --"); |
| 111 | + let mut r = ReservoirU32::new(1000); |
| 112 | + dists[..5000].iter().for_each(|&d| r.observe(d)); |
| 113 | + const K: usize = 20_000; |
| 114 | + ns_per("5. shape: sort + median + kurtosis", K, || { |
| 115 | + for _ in 0..K { |
| 116 | + let sorted = black_box(&r).sorted(); |
| 117 | + black_box(quantile_of_sorted(&sorted, 5000)); |
| 118 | + black_box(r.kurtosis(8192, 64)); |
| 119 | + } |
| 120 | + }); |
| 121 | + let lattice = [4u8, 6, 7, 8, 9, 10, 11, 12].map(SigmaLevel); |
| 122 | + let shape = EmpiricalShape::from_sample(r.samples()).unwrap(); |
| 123 | + ns_per("6. empirical: locate 8 lattice levels", K, || { |
| 124 | + for _ in 0..K { |
| 125 | + black_box(lattice.map(|l| black_box(&shape).locate(l, 8200, 70))); |
| 126 | + } |
| 127 | + }); |
| 128 | + |
| 129 | + println!("-- query path, on demand --"); |
| 130 | + let mut g = RollingFloor::for_width(16384); |
| 131 | + dists[..3000].iter().for_each(|&d| { |
| 132 | + if let Some(s) = g.observe(d) { |
| 133 | + g.recalibrate(&s); |
| 134 | + } |
| 135 | + }); |
| 136 | + ns_per("7. Gaussian: thresholds of 8 levels", N, || { |
| 137 | + for _ in 0..N { |
| 138 | + black_box(black_box(&g).thresholds(&lattice)); |
| 139 | + } |
| 140 | + }); |
| 141 | + ns_per("8. Gaussian: shade over 8 levels", N, || { |
| 142 | + let mut acc = 0usize; |
| 143 | + for &d in &dists { |
| 144 | + acc += black_box(&g).shade(d, &lattice); |
| 145 | + } |
| 146 | + black_box(acc); |
| 147 | + }); |
| 148 | +} |
0 commit comments