use crate::bench::{Bencher as ZenBencher, Suite};
pub use crate::bench::Throughput;
pub use crate::black_box;
pub struct BenchmarkId(String);
impl BenchmarkId {
pub fn new<S: std::fmt::Display, P: std::fmt::Display>(function_name: S, parameter: P) -> Self {
Self(format!("{function_name}/{parameter}"))
}
pub fn from_parameter<P: std::fmt::Display>(parameter: P) -> Self {
Self(format!("{parameter}"))
}
}
impl std::fmt::Display for BenchmarkId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<BenchmarkId> for String {
fn from(id: BenchmarkId) -> String {
id.0
}
}
pub struct Criterion {
suite: Suite,
config_max_rounds: Option<usize>,
config_max_time: Option<std::time::Duration>,
config_warmup_time: Option<std::time::Duration>,
config_noise_threshold: Option<f64>,
}
impl Criterion {
#[allow(clippy::should_implement_trait)]
pub fn default() -> Self {
Self {
suite: Suite::new(),
config_max_rounds: None,
config_max_time: None,
config_warmup_time: None,
config_noise_threshold: None,
}
}
pub fn sample_size(&mut self, n: usize) -> &mut Self {
self.config_max_rounds = Some(n);
self
}
pub fn measurement_time(&mut self, dur: std::time::Duration) -> &mut Self {
self.config_max_time = Some(dur);
self
}
pub fn warm_up_time(&mut self, dur: std::time::Duration) -> &mut Self {
self.config_warmup_time = Some(dur);
self
}
pub fn significance_level(&mut self, _level: f64) -> &mut Self {
self
}
pub fn noise_threshold(&mut self, threshold: f64) -> &mut Self {
self.config_noise_threshold = Some(threshold);
self
}
pub fn benchmark_group<S: Into<String>>(&mut self, name: S) -> BenchmarkGroup<'_> {
BenchmarkGroup {
name: name.into(),
suite: &mut self.suite,
group: None,
immediate_results: Vec::new(),
config_max_rounds: self.config_max_rounds,
config_max_time: self.config_max_time,
config_warmup_time: self.config_warmup_time,
config_noise_threshold: self.config_noise_threshold,
}
}
pub fn bench_function<S, F>(&mut self, id: S, mut f: F) -> &mut Self
where
S: Into<String>,
F: FnMut(&mut Bencher),
{
let mut group = self.benchmark_group(id);
group.bench_function("_", &mut f);
group.finish();
self
}
pub fn bench_with_input<S, I, F>(&mut self, id: S, input: &I, mut f: F) -> &mut Self
where
S: Into<String>,
I: Clone,
F: FnMut(&mut Bencher, &I),
{
let mut group = self.benchmark_group(id);
group.bench_with_input("_", input, &mut f);
group.finish();
self
}
#[doc(hidden)]
pub fn into_suite(self) -> Suite {
self.suite
}
}
pub struct BenchmarkGroup<'a> {
name: String,
suite: &'a mut Suite,
group: Option<crate::bench::BenchGroup>,
immediate_results: Vec<crate::results::BenchmarkResult>,
config_max_rounds: Option<usize>,
config_max_time: Option<std::time::Duration>,
config_warmup_time: Option<std::time::Duration>,
config_noise_threshold: Option<f64>,
}
impl<'a> BenchmarkGroup<'a> {
fn ensure_group(&mut self) -> &mut crate::bench::BenchGroup {
if self.group.is_none() {
let mut g = crate::bench::BenchGroup::new_public(&self.name);
if let Some(n) = self.config_max_rounds {
g.config().max_rounds(n);
}
if let Some(d) = self.config_max_time {
g.config().max_time(d);
}
if let Some(d) = self.config_warmup_time {
g.config().warmup_time(d);
}
if let Some(t) = self.config_noise_threshold {
g.config().noise_threshold(t);
}
self.group = Some(g);
}
self.group.as_mut().unwrap()
}
pub fn throughput(&mut self, throughput: Throughput) -> &mut Self {
self.ensure_group().throughput(throughput);
self
}
pub fn throughput_unit(&mut self, unit: impl Into<String>) -> &mut Self {
self.ensure_group().throughput_unit(unit);
self
}
pub fn baseline(&mut self, name: impl Into<String>) -> &mut Self {
self.ensure_group().baseline(name);
self
}
pub fn sample_size(&mut self, n: usize) -> &mut Self {
self.config_max_rounds = Some(n);
self
}
pub fn measurement_time(&mut self, dur: std::time::Duration) -> &mut Self {
self.config_max_time = Some(dur);
self
}
pub fn warm_up_time(&mut self, dur: std::time::Duration) -> &mut Self {
self.config_warmup_time = Some(dur);
self
}
pub fn sampling_mode(&mut self, _mode: impl std::fmt::Debug) -> &mut Self {
self
}
pub fn plot_config(&mut self, _config: impl std::fmt::Debug) -> &mut Self {
self
}
pub fn significance_level(&mut self, _level: f64) -> &mut Self {
self
}
pub fn nresamples(&mut self, _n: usize) -> &mut Self {
self
}
pub fn sort_by_speed(&mut self) -> &mut Self {
self.ensure_group().config().sort_by_speed(true);
self
}
pub fn subgroup(&mut self, label: impl Into<String>) -> &mut Self {
self.ensure_group().subgroup(label);
self
}
pub fn bench_function<S, F>(&mut self, id: S, mut f: F) -> &mut Self
where
S: Into<String>,
F: FnMut(&mut Bencher),
{
let name = id.into();
let config = self.get_config();
let mut bencher = crate::bench::Bencher::new(1);
f(&mut Bencher(&mut bencher));
let per_iter = bencher.elapsed_ns.max(1);
let timer_res = crate::platform::timer_resolution_ns();
let precision_min = timer_res.saturating_mul(1000).max(10_000);
let iters = ((config.sample_target_ns.max(precision_min)) / per_iter).max(1) as usize;
let iters = iters.clamp(config.min_iterations, config.max_iterations);
let n_rounds = config.max_rounds.min(100);
let mut samples = Vec::with_capacity(n_rounds);
for _ in 0..n_rounds {
let mut b = crate::bench::Bencher::new(iters);
f(&mut Bencher(&mut b));
samples.push(b.elapsed_ns as f64 / iters as f64);
}
let summary = crate::stats::Summary::from_slice(&samples);
let mean_ci = crate::stats::MeanCi::from_samples(&samples, config.bootstrap_resamples);
self.immediate_results
.push(crate::results::BenchmarkResult {
name,
summary,
mean_ci,
..Default::default()
});
self
}
pub fn bench_with_input<S, I, F>(&mut self, id: S, input: &I, mut f: F) -> &mut Self
where
S: Into<String>,
I: Clone,
F: FnMut(&mut Bencher, &I),
{
let input = input.clone();
self.bench_function(id, |b| f(b, &input))
}
fn get_config(&self) -> crate::bench::GroupConfig {
let mut config = crate::bench::GroupConfig::default();
if let Some(n) = self.config_max_rounds {
config.max_rounds = n;
}
if let Some(d) = self.config_max_time {
config.max_time = d;
}
if let Some(d) = self.config_warmup_time {
config.warmup_time = d;
}
if let Some(t) = self.config_noise_threshold {
config.noise_threshold = t;
}
config
}
pub fn finish(mut self) {
self.commit();
}
fn commit(&mut self) {
if !self.immediate_results.is_empty() {
let comp = crate::results::ComparisonResult {
group_name: self.name.clone(),
benchmarks: std::mem::take(&mut self.immediate_results),
..Default::default()
};
crate::report::print_group(&comp, crate::platform::timer_resolution_ns());
self.suite.push_comparison(comp);
} else if let Some(group) = self.group.take() {
self.suite.push_group(group);
}
}
}
impl Drop for BenchmarkGroup<'_> {
fn drop(&mut self) {
self.commit();
}
}
pub struct Bencher<'a>(&'a mut ZenBencher);
impl<'a> Bencher<'a> {
pub fn iter<O, F: FnMut() -> O>(&mut self, f: F) {
self.0.iter(f);
}
pub fn iter_batched<I, O, S, R>(&mut self, setup: S, routine: R, _batch_size: BatchSize)
where
S: FnMut() -> I + 'static,
R: FnMut(I) -> O,
{
self.0.with_input(setup).run(routine);
}
pub fn iter_batched_ref<I, O, S, R>(&mut self, setup: S, mut routine: R, _batch_size: BatchSize)
where
S: FnMut() -> I + 'static,
R: FnMut(&mut I) -> O,
{
self.0
.with_input(setup)
.run(move |mut input| routine(&mut input));
}
}
#[derive(Debug, Clone, Copy)]
pub enum BatchSize {
SmallInput,
LargeInput,
PerIteration,
NumBatches(u64),
NumIterations(u64),
}
#[macro_export]
macro_rules! criterion_group {
($name:ident, $($func:path),+ $(,)?) => {
fn $name() -> $crate::criterion_compat::Criterion {
let mut criterion = $crate::criterion_compat::Criterion::default();
$(
$func(&mut criterion);
)+
criterion
}
};
}
#[macro_export]
macro_rules! criterion_main {
($($group:path),+ $(,)?) => {
fn main() {
let mut suite = $crate::Suite::new();
let group_filter: Option<String> = std::env::args()
.find_map(|a| a.strip_prefix("--group=").map(String::from));
if let Some(ref filter) = group_filter {
suite.set_group_filter(filter.clone());
}
$(
let criterion = $group();
suite.merge(criterion.into_suite());
)+
let engine = $crate::engine_new(suite);
let result = engine.run();
$crate::postprocess_result(&result);
}
};
}