slain-num 0.3.0

SLAIN's own numeric crate: dense matrices, least squares, polynomials, FFT, convolution, statistics and 1-D/N-D minimisation, with AVX2 kernels.
Documentation
use std::time::{Duration, Instant};

pub use core::hint::black_box as slain;

pub struct Bencher {
    iters: u64,
    ticks: u64,
}

impl Bencher {
    pub fn iter<R>(&mut self, mut f: impl FnMut() -> R) {
        let t0 = clock::now();
        for _ in 0..self.iters {
            slain(f());
        }
        self.ticks = clock::now().wrapping_sub(t0);
    }
}

type Arm<'a> = (String, Box<dyn FnMut(&mut Bencher) + 'a>);

pub struct Group<'a> {
    name: String,
    arms: Vec<Arm<'a>>,
    cfg: Config,
}

#[derive(Clone, Copy)]
struct Config {
    warm: Duration,
    sample: Duration,
    rounds: usize,
}

impl Default for Config {
    fn default() -> Self {
        Self { warm: Duration::from_millis(300), sample: Duration::from_micros(2000), rounds: 41 }
    }
}

impl<'a> Group<'a> {
    pub fn bench_function(&mut self, name: impl Into<String>, f: impl FnMut(&mut Bencher) + 'a) -> &mut Self {
        self.arms.push((name.into(), Box::new(f)));
        self
    }

    pub fn finish(mut self) {
        if self.arms.is_empty() {
            return;
        }
        let report = run(&self.name, &mut self.arms, self.cfg);
        print!("{report}");
    }
}

#[derive(Default)]
pub struct Harness {
    filter: Option<String>,
}

impl Harness {
    #[must_use]
    pub fn from_args() -> Self {
        pin_and_raise();
        Self { filter: std::env::args().skip(1).find(|a| !a.starts_with('-')) }
    }

    pub fn benchmark_group<'a>(&mut self, name: impl Into<String>) -> Group<'a> {
        let name = name.into();
        let skip = self.filter.as_ref().is_some_and(|f| !name.contains(f.as_str()));
        Group { name, arms: Vec::new(), cfg: if skip { Config { rounds: 0, ..Config::default() } } else { Config::default() } }
    }
}

#[macro_export]
macro_rules! bench_main {
    ($($f:path),+ $(,)?) => {
        fn main() {
            let mut h = $crate::bench::Harness::from_args();
            $($f(&mut h);)+
        }
    };
}

struct Measured {
    name: String,
    ns: Vec<f64>,
}

fn run(group: &str, arms: &mut [Arm<'_>], cfg: Config) -> String {
    if cfg.rounds == 0 {
        return String::new();
    }
    let mut slots: Vec<usize> = (0..arms.len()).collect();
    slots.push(0);
    let iters: Vec<u64> = (0..arms.len()).map(|a| calibrate(&mut arms[a].1, cfg.sample)).collect();
    let t = Instant::now();
    while t.elapsed() < cfg.warm {
        for (a, arm) in arms.iter_mut().enumerate() {
            sample(&mut arm.1, iters[a]);
        }
    }
    let overhead = timer_overhead();
    let mut data: Vec<Measured> = slots
        .iter()
        .enumerate()
        .map(|(s, &a)| Measured { name: if s >= arms.len() { format!("{} (A/A control)", arms[a].0) } else { arms[a].0.clone() }, ns: Vec::with_capacity(cfg.rounds) })
        .collect();
    for round in 0..cfg.rounds {
        for k in 0..slots.len() {
            let s = (k + round) % slots.len();
            let a = slots[s];
            let ticks = sample(&mut arms[a].1, iters[a]).saturating_sub(overhead);
            data[s].ns.push(clock::to_ns(ticks) / iters[a] as f64);
        }
    }
    report(group, &data)
}

fn calibrate(f: &mut Box<dyn FnMut(&mut Bencher) + '_>, target: Duration) -> u64 {
    let mut iters = 1u64;
    loop {
        let t = Instant::now();
        sample(f, iters);
        let e = t.elapsed();
        if e >= target || iters >= 1 << 40 {
            return iters;
        }
        let scale = (target.as_secs_f64() / e.as_secs_f64().max(1e-9)).clamp(1.5, 100.0);
        iters = ((iters as f64) * scale).ceil() as u64;
    }
}

fn sample(f: &mut Box<dyn FnMut(&mut Bencher) + '_>, iters: u64) -> u64 {
    let mut b = Bencher { iters, ticks: 0 };
    f(&mut b);
    b.ticks
}

fn timer_overhead() -> u64 {
    let mut best = u64::MAX;
    for _ in 0..1000 {
        let t0 = clock::now();
        best = best.min(clock::now().wrapping_sub(t0));
    }
    best
}

fn median(v: &mut [f64]) -> f64 {
    v.sort_by(f64::total_cmp);
    let n = v.len();
    if n == 0 {
        f64::NAN
    } else if n % 2 == 1 {
        v[n / 2]
    } else {
        (v[n / 2 - 1] + v[n / 2]) / 2.0
    }
}

fn bootstrap(x: &[f64]) -> (f64, f64) {
    let mut state = 0x9E37_79B9_7F4A_7C15u64;
    let mut next = || {
        state ^= state << 13;
        state ^= state >> 7;
        state ^= state << 17;
        state
    };
    let n = x.len();
    let mut meds: Vec<f64> = (0..2000)
        .map(|_| {
            let mut s: Vec<f64> = (0..n).map(|_| x[(next() % n as u64) as usize]).collect();
            median(&mut s)
        })
        .collect();
    meds.sort_by(f64::total_cmp);
    (meds[50], meds[1949])
}

fn report(group: &str, data: &[Measured]) -> String {
    use std::fmt::Write;
    let mut out = String::new();
    let base = &data[0].ns;
    let speed = |m: &Measured| -> Vec<f64> { m.ns.iter().zip(base).map(|(a, b)| b / a).collect() };
    let control = data.iter().find(|m| m.name.ends_with("(A/A control)"));
    let noise = control.map_or(0.0, |c| {
        let (lo, hi) = bootstrap(&speed(c));
        (1.0 - lo).abs().max((hi - 1.0).abs())
    });
    let _ = writeln!(out, "\n{group}  (noise floor ±{:.9}%)", noise * 100.0);
    for m in data {
        let med = median(&mut m.ns.clone());
        let r = speed(m);
        let (lo, hi) = bootstrap(&r);
        let rm = median(&mut r.clone());
        let verdict = if std::ptr::eq(m, &data[0]) {
            "baseline".to_string()
        } else if lo > 1.0 + noise {
            format!("faster {rm:.9}x")
        } else if hi < 1.0 - noise {
            format!("slower {:.9}x", 1.0 / rm)
        } else {
            "inside noise".to_string()
        };
        let _ = writeln!(out, "  {:<34} {:>14}  speed {:.9} [{:.9}, {:.9}]  {verdict}", m.name, fmt_rate(1e9 / med), rm, lo, hi);
    }
    out
}

fn fmt_rate(per_s: f64) -> String {
    if per_s >= 1e9 {
        format!("{:.9} billion/s", per_s / 1e9)
    } else if per_s >= 1e6 {
        format!("{:.9} million/s", per_s / 1e6)
    } else if per_s >= 1e3 {
        format!("{:.9} thousand/s", per_s / 1e3)
    } else {
        format!("{per_s:.9} per second")
    }
}

fn pin_and_raise() {
    #[cfg(windows)]
    {
        #[link(name = "kernel32")]
        extern "system" {
            fn GetCurrentThread() -> *mut core::ffi::c_void;
            fn SetThreadAffinityMask(thread: *mut core::ffi::c_void, mask: usize) -> usize;
            fn SetThreadPriority(thread: *mut core::ffi::c_void, priority: i32) -> i32;
        }
        const THREAD_PRIORITY_HIGHEST: i32 = 2;
        unsafe {
            let t = GetCurrentThread();
            SetThreadAffinityMask(t, 1usize << 2);
            SetThreadPriority(t, THREAD_PRIORITY_HIGHEST);
        }
    }
}

mod clock {
    use std::sync::OnceLock;
    use std::time::Instant;

    fn origin() -> Instant {
        static O: OnceLock<Instant> = OnceLock::new();
        *O.get_or_init(Instant::now)
    }

    #[inline(always)]
    pub fn now() -> u64 {
        #[cfg(target_arch = "x86_64")]
        {
            unsafe { core::arch::x86_64::_rdtsc() }
        }
        #[cfg(not(target_arch = "x86_64"))]
        {
            origin().elapsed().as_nanos() as u64
        }
    }

    fn ns_per_tick() -> f64 {
        static K: OnceLock<f64> = OnceLock::new();
        *K.get_or_init(|| {
            if cfg!(not(target_arch = "x86_64")) {
                return 1.0;
            }
            let (t0, c0) = (origin().elapsed(), now());
            let spin = Instant::now();
            while spin.elapsed().as_millis() < 50 {
                std::hint::spin_loop();
            }
            let (t1, c1) = (origin().elapsed(), now());
            (t1 - t0).as_nanos() as f64 / c1.wrapping_sub(c0).max(1) as f64
        })
    }

    pub fn to_ns(ticks: u64) -> f64 {
        ticks as f64 * ns_per_tick()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn detects_a_real_difference_and_passes_the_control() {
        let v: Vec<u64> = (0..4096).collect();
        let mut arms: Vec<Arm<'_>> = vec![
            ("sum".into(), Box::new(|b: &mut Bencher| b.iter(|| slain(&v).iter().sum::<u64>()))),
            ("sum x4".into(), Box::new(|b: &mut Bencher| b.iter(|| (0..4).map(|_| slain(&v).iter().sum::<u64>()).sum::<u64>()))),
        ];
        let cfg = Config { warm: Duration::from_millis(20), sample: Duration::from_micros(200), rounds: 15 };
        let out = run("test", &mut arms, cfg);
        assert!(out.contains("slower"), "{out}");
        assert!(out.contains("(A/A control)"), "{out}");
        assert!(out.contains("/s"), "{out}");
    }
}