Skip to main content

scalar_baseline/
scalar_baseline.rs

1//! Scalar encode baseline (M3, ledger). Deterministic work: encodes one
2//! stripe `reps` times and prints the work counts, per-rep wall stats, and a
3//! parity checksum (defeats dead-code elimination; doubles as a determinism
4//! check across runs). The pinned harness (`tools/bench/scalar_baseline.ps1`)
5//! derives GB/s from the PROCESS CPU time per codec-measurement §1–§2 —
6//! wall time printed here is informational only.
7//!
8//! Usage: scalar_baseline <k> <p> <shard_len> <reps>
9
10use std::hint::black_box;
11use std::time::Instant;
12
13use rusty_erasure_core::{Coder, Matrix};
14
15fn main() {
16    let args: Vec<usize> = std::env::args()
17        .skip(1)
18        .filter_map(|a| a.parse().ok())
19        .collect();
20    let &[k, p, len, reps] = args.as_slice() else {
21        eprintln!("usage: scalar_baseline <k> <p> <shard_len> <reps>");
22        std::process::exit(2);
23    };
24
25    let coder = match Matrix::cauchy(k, p).and_then(Coder::new) {
26        Ok(c) => c,
27        Err(e) => {
28            eprintln!("bad config: {e}");
29            std::process::exit(2);
30        }
31    };
32
33    // Deterministic data (splitmix64) so every run encodes identical bytes.
34    let mut state: u64 = (k as u64) << 32 | (p as u64) << 16 | len as u64;
35    let mut next = move || {
36        state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
37        let mut z = state;
38        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
39        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
40        z ^ (z >> 31)
41    };
42    let data: Vec<Vec<u8>> = (0..k)
43        .map(|_| (0..len).map(|_| next() as u8).collect())
44        .collect();
45    let data_refs: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
46    let mut parity = vec![vec![0u8; len]; p];
47
48    // Warmup (untimed), then the timed reps.
49    for _ in 0..3 {
50        let mut refs: Vec<&mut [u8]> = parity.iter_mut().map(|b| b.as_mut_slice()).collect();
51        coder.encode(&data_refs, &mut refs).expect("encode");
52    }
53    let mut per_rep_ns: Vec<u128> = Vec::with_capacity(reps);
54    let total = Instant::now();
55    for _ in 0..reps {
56        let t = Instant::now();
57        let mut refs: Vec<&mut [u8]> = parity.iter_mut().map(|b| b.as_mut_slice()).collect();
58        coder
59            .encode(&data_refs, black_box(&mut refs))
60            .expect("encode");
61        per_rep_ns.push(t.elapsed().as_nanos());
62        black_box(&parity);
63    }
64    let wall = total.elapsed();
65
66    let checksum = parity.iter().flatten().fold(0u8, |a, &b| a ^ b);
67    per_rep_ns.sort_unstable();
68    let src_bytes = (k * len) as u128 * reps as u128;
69    let mul_count = (k * p * len) as u128 * reps as u128;
70    println!("cell k={k} p={p} len={len} reps={reps}");
71    println!("work: source_bytes={src_bytes} table_muls={mul_count} checksum={checksum:#04x}");
72    println!(
73        "wall: total_ms={} rep_min_us={} rep_median_us={}",
74        wall.as_millis(),
75        per_rep_ns.first().unwrap_or(&0) / 1000,
76        per_rep_ns.get(reps / 2).unwrap_or(&0) / 1000,
77    );
78}