#![cfg_attr(feature="nightly", feature(test))]
#![warn(missing_copy_implementations, missing_debug_implementations, missing_docs)]
mod utility;
pub mod statistics;
pub mod time;
use std::cmp;
use std::mem;
use std::time::{Duration};
use crate::statistics::{Model};
use crate::time::{Nanoseconds, Stopwatch};
use crate::utility::{GeometricSequence, black_box, format_number};
const ITERATIONS: u64 = 1_000_000_000_000_000;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Bytes(pub u64);
impl Bytes {
pub fn kibibytes(kibibytes: u64) -> Self {
Bytes(kibibytes * 1024)
}
pub fn mebibytes(mebibytes: u64) -> Self {
Bytes(mebibytes * 1024 * 1024)
}
pub fn gibibytes(gibibytes: u64) -> Self {
Bytes(gibibytes * 1024 * 1024 * 1024)
}
}
#[derive(Copy, Clone, Debug)]
pub struct Options {
factor: f64,
memory: Bytes,
time: Nanoseconds<u64>,
}
impl Options {
pub fn factor(mut self, factor: f64) -> Self {
self.factor = factor;
self
}
pub fn memory(mut self, memory: Bytes) -> Self {
self.memory = memory;
self
}
pub fn time(mut self, time: Duration) -> Self {
self.time = time.into();
self
}
}
impl Default for Options {
fn default() -> Self {
let factor = 1.01;
let memory = Bytes::mebibytes(512);
let time = Duration::new(5, 0).into();
Options { factor, memory, time }
}
}
#[derive(Copy, Clone, Debug)]
pub struct Sample {
pub iterations: u64,
pub elapsed: Nanoseconds<u64>,
}
#[derive(Copy, Clone, Debug)]
pub struct Analysis {
pub alpha: Nanoseconds<f64>,
pub beta: Nanoseconds<f64>,
pub r2: f64,
}
impl Analysis {
fn new(samples: &[Sample]) -> Self {
let Model { alpha, beta, r2 } = samples.iter()
.map(|m| (m.iterations as f64, m.elapsed.0 as f64))
.collect::<Model>();
Self { alpha: Nanoseconds(alpha), beta: Nanoseconds(beta), r2 }
}
}
pub fn bench<T>(options: &Options, name: &str, f: impl FnMut() -> T) {
bench_impl(name, move || measure(options, f));
}
pub fn bench_drop<T>(options: &Options, name: &str, f: impl FnMut() -> T) {
bench_impl(name, move || measure_drop(options, f));
}
pub fn bench_setup<I, T>(
options: &Options,
name: &str,
setup: impl FnMut() -> I,
f: impl FnMut(I) -> T,
) {
bench_impl(name, move || measure_setup(options, setup, f));
}
pub fn measure<T>(
options: &Options, mut f: impl FnMut() -> T
) -> Vec<Sample> {
measure_impl(options, |iterations| {
let stopwatch = Stopwatch::default();
for _ in 0..iterations { retain(f()); }
Some(stopwatch.elapsed())
})
}
pub fn measure_drop<T>(
options: &Options, mut f: impl FnMut() -> T
) -> Vec<Sample> {
measure_impl(options, |iterations| {
let size = cmp::max(1, mem::size_of::<T>() as u64);
if options.memory < Bytes(iterations * size) {
return None;
}
let mut outputs = Vec::with_capacity(iterations as usize);
let stopwatch = Stopwatch::default();
for _ in 0..iterations { outputs.push(f()); }
let elapsed = stopwatch.elapsed();
mem::drop(outputs);
Some(elapsed)
})
}
pub fn measure_setup<I, T>(
options: &Options,
mut setup: impl FnMut() -> I,
mut f: impl FnMut(I) -> T,
) -> Vec<Sample> {
measure_impl(options, |iterations| {
let size = cmp::max(1, mem::size_of::<I>() as u64);
if options.memory < Bytes(iterations * size) {
return None;
}
let inputs = retain((0..iterations).map(|_| setup()).collect::<Vec<_>>());
let stopwatch = Stopwatch::default();
for input in inputs { retain(f(input)); }
Some(stopwatch.elapsed())
})
}
pub fn retain<T>(value: T) -> T {
black_box(value)
}
fn bench_impl(name: &str, f: impl FnOnce() -> Vec<Sample>) {
let stopwatch = Stopwatch::default();
let samples = f();
let elapsed = stopwatch.elapsed();
let analysis = Analysis::new(&samples);
let prefix = format!("{} ({}) ...", name, elapsed);
if samples.len() < 2 || analysis.beta.0 < 0.0 {
println!("{:<32} {:>15}", prefix, " not enough samples");
} else {
let beta = format_number(analysis.beta.0, 3, '_');
println!("{:<32} {:>15} ns/iter ({:.3} R²)", prefix, beta, analysis.r2);
}
}
fn measure_impl(
options: &Options, mut f: impl FnMut(u64) -> Option<Nanoseconds<u64>>
) -> Vec<Sample> {
let stopwatch = Stopwatch::default();
GeometricSequence::new(1, options.factor)
.take_while(|i| *i <= ITERATIONS && stopwatch.elapsed() < options.time)
.filter_map(|i| Some(Sample { iterations: i, elapsed: f(i)? }))
.collect()
}