#![allow(deprecated)] use crate::analysis; use crate::connection::OutgoingMessage; use crate::measurement::{Measurement, WallTime}; use crate::report::{BenchmarkId, Report, ReportContext}; use crate::routine::{Function, Routine}; use crate::{Bencher, Criterion, DurationExt, Mode, PlotConfiguration, SamplingMode, Throughput}; use std::cell::RefCell; use std::fmt::Debug; use std::marker::Sized; use std::time::Duration; // TODO: Move the benchmark config stuff to a separate module for easier use. /// Struct containing all of the configuration options for a benchmark. pub struct BenchmarkConfig { pub confidence_level: f64, pub measurement_time: Duration, pub noise_threshold: f64, pub nresamples: usize, pub sample_size: usize, pub significance_level: f64, pub warm_up_time: Duration, pub sampling_mode: SamplingMode, } /// Struct representing a partially-complete per-benchmark configuration. #[derive(Clone)] pub(crate) struct PartialBenchmarkConfig { pub(crate) confidence_level: Option, pub(crate) measurement_time: Option, pub(crate) noise_threshold: Option, pub(crate) nresamples: Option, pub(crate) sample_size: Option, pub(crate) significance_level: Option, pub(crate) warm_up_time: Option, pub(crate) sampling_mode: Option, pub(crate) plot_config: PlotConfiguration, } impl Default for PartialBenchmarkConfig { fn default() -> Self { PartialBenchmarkConfig { confidence_level: None, measurement_time: None, noise_threshold: None, nresamples: None, sample_size: None, significance_level: None, warm_up_time: None, plot_config: PlotConfiguration::default(), sampling_mode: None, } } } impl PartialBenchmarkConfig { pub(crate) fn to_complete(&self, defaults: &BenchmarkConfig) -> BenchmarkConfig { BenchmarkConfig { confidence_level: self.confidence_level.unwrap_or(defaults.confidence_level), measurement_time: self.measurement_time.unwrap_or(defaults.measurement_time), noise_threshold: self.noise_threshold.unwrap_or(defaults.noise_threshold), nresamples: self.nresamples.unwrap_or(defaults.nresamples), sample_size: self.sample_size.unwrap_or(defaults.sample_size), significance_level: self .significance_level .unwrap_or(defaults.significance_level), warm_up_time: self.warm_up_time.unwrap_or(defaults.warm_up_time), sampling_mode: self.sampling_mode.unwrap_or(defaults.sampling_mode), } } } pub(crate) struct NamedRoutine { pub id: String, pub(crate) f: Box>>, } /// Structure representing a benchmark (or group of benchmarks) /// which take one parameter. #[doc(hidden)] #[deprecated(since = "0.3.4", note = "Please use BenchmarkGroups instead.")] pub struct ParameterizedBenchmark { config: PartialBenchmarkConfig, values: Vec, routines: Vec>, throughput: Option Throughput>>, } /// Structure representing a benchmark (or group of benchmarks) /// which takes no parameters. #[doc(hidden)] #[deprecated(since = "0.3.4", note = "Please use BenchmarkGroups instead.")] pub struct Benchmark { config: PartialBenchmarkConfig, routines: Vec>, throughput: Option, } /// Common trait for `Benchmark` and `ParameterizedBenchmark`. Not intended to be /// used outside of Criterion.rs. #[doc(hidden)] pub trait BenchmarkDefinition: Sized { #[doc(hidden)] fn run(self, group_id: &str, c: &mut Criterion); } macro_rules! benchmark_config { ($type:tt) => { /// Changes the size of the sample for this benchmark /// /// A bigger sample should yield more accurate results if paired with a sufficiently large /// measurement time. /// /// Sample size must be at least 10. /// /// # Panics /// /// Panics if n < 10. pub fn sample_size(mut self, n: usize) -> Self { assert!(n >= 10); self.config.sample_size = Some(n); self } /// Changes the warm up time for this benchmark /// /// # Panics /// /// Panics if the input duration is zero pub fn warm_up_time(mut self, dur: Duration) -> Self { assert!(dur.to_nanos() > 0); self.config.warm_up_time = Some(dur); self } /// Changes the target measurement time for this benchmark. Criterion will attempt /// to spend approximately this amount of time measuring the benchmark. /// With a longer time, the measurement will become more resilient to transitory peak loads /// caused by external programs. /// /// # Panics /// /// Panics if the input duration in zero pub fn measurement_time(mut self, dur: Duration) -> Self { assert!(dur.to_nanos() > 0); self.config.measurement_time = Some(dur); self } /// Changes the number of resamples for this benchmark /// /// Number of resamples to use for the /// [bootstrap](http://en.wikipedia.org/wiki/Bootstrapping_(statistics)#Case_resampling) /// /// A larger number of resamples reduces the random sampling errors, which are inherent to the /// bootstrap method, but also increases the analysis time. /// /// # Panics /// /// Panics if the number of resamples is set to zero pub fn nresamples(mut self, n: usize) -> Self { assert!(n > 0); if n <= 1000 { println!("\nWarning: It is not recommended to reduce nresamples below 1000."); } self.config.nresamples = Some(n); self } /// Changes the default noise threshold for this benchmark. The noise threshold /// is used to filter out small changes in performance, even if they are statistically /// significant. Sometimes benchmarking the same code twice will result in small but /// statistically significant differences solely because of noise. This provides a way to filter /// out some of these false positives at the cost of making it harder to detect small changes /// to the true performance of the benchmark. /// /// The default is 0.01, meaning that changes smaller than 1% will be ignored. /// /// # Panics /// /// Panics if the threshold is set to a negative value pub fn noise_threshold(mut self, threshold: f64) -> Self { assert!(threshold >= 0.0); self.config.noise_threshold = Some(threshold); self } /// Changes the default confidence level for this benchmark. The confidence /// level is the desired probability that the true runtime lies within the estimated /// [confidence interval](https://en.wikipedia.org/wiki/Confidence_interval). The default is /// 0.95, meaning that the confidence interval should capture the true value 95% of the time. /// /// # Panics /// /// Panics if the confidence level is set to a value outside the `(0, 1)` range pub fn confidence_level(mut self, cl: f64) -> Self { assert!(cl > 0.0 && cl < 1.0); if cl < 0.5 { println!("\nWarning: It is not recommended to reduce confidence level below 0.5."); } self.config.confidence_level = Some(cl); self } /// Changes the default [significance level](https://en.wikipedia.org/wiki/Statistical_significance) /// for this benchmark. This is used to perform a /// [hypothesis test](https://en.wikipedia.org/wiki/Statistical_hypothesis_testing) to see if /// the measurements from this run are different from the measured performance of the last run. /// The significance level is the desired probability that two measurements of identical code /// will be considered 'different' due to noise in the measurements. The default value is 0.05, /// meaning that approximately 5% of identical benchmarks will register as different due to /// noise. /// /// This presents a trade-off. By setting the significance level closer to 0.0, you can increase /// the statistical robustness against noise, but it also weakens Criterion.rs' ability to /// detect small but real changes in the performance. By setting the significance level /// closer to 1.0, Criterion.rs will be more able to detect small true changes, but will also /// report more spurious differences. /// /// See also the noise threshold setting. /// /// # Panics /// /// Panics if the significance level is set to a value outside the `(0, 1)` range pub fn significance_level(mut self, sl: f64) -> Self { assert!(sl > 0.0 && sl < 1.0); self.config.significance_level = Some(sl); self } /// Changes the plot configuration for this benchmark. pub fn plot_config(mut self, new_config: PlotConfiguration) -> Self { self.config.plot_config = new_config; self } /// Changes the sampling mode for this benchmark. pub fn sampling_mode(mut self, new_mode: SamplingMode) -> Self { self.config.sampling_mode = Some(new_mode); self } }; } impl Benchmark where M: Measurement + 'static, { benchmark_config!(Benchmark); /// Create a new benchmark group and adds the given function to it. /// /// # Example /// /// ```rust /// # #[macro_use] extern crate criterion; /// # use criterion::*; /// /// fn bench(c: &mut Criterion) { /// // One-time setup goes here /// c.bench( /// "my_group", /// Benchmark::new("my_function", |b| b.iter(|| { /// // Code to benchmark goes here /// })), /// ); /// } /// /// criterion_group!(benches, bench); /// criterion_main!(benches); /// ``` pub fn new(id: S, f: F) -> Benchmark where S: Into, F: FnMut(&mut Bencher<'_, M>) + 'static, { Benchmark { config: PartialBenchmarkConfig::default(), routines: vec![], throughput: None, } .with_function(id, f) } /// Add a function to the benchmark group. pub fn with_function(mut self, id: S, mut f: F) -> Benchmark where S: Into, F: FnMut(&mut Bencher<'_, M>) + 'static, { let routine = NamedRoutine { id: id.into(), f: Box::new(RefCell::new(Function::new(move |b, _| f(b)))), }; self.routines.push(routine); self } /// Set the input size for this benchmark group. Used for reporting the /// throughput. pub fn throughput(mut self, throughput: Throughput) -> Benchmark { self.throughput = Some(throughput); self } } impl BenchmarkDefinition for Benchmark { fn run(self, group_id: &str, c: &mut Criterion) { let report_context = ReportContext { output_directory: c.output_directory.clone(), plot_config: self.config.plot_config.clone(), }; let config = self.config.to_complete(&c.config); let num_routines = self.routines.len(); let mut all_ids = vec![]; let mut any_matched = false; if let Some(conn) = &c.connection { conn.send(&OutgoingMessage::BeginningBenchmarkGroup { group: group_id }) .unwrap(); } for routine in self.routines { let function_id = if num_routines == 1 && group_id == routine.id { None } else { Some(routine.id) }; let mut id = BenchmarkId::new( group_id.to_owned(), function_id, None, self.throughput.clone(), ); id.ensure_directory_name_unique(&c.all_directories); c.all_directories.insert(id.as_directory_name().to_owned()); id.ensure_title_unique(&c.all_titles); c.all_titles.insert(id.as_title().to_owned()); let do_run = c.filter_matches(id.id()); any_matched |= do_run; execute_benchmark( do_run, &id, c, &config, &mut *routine.f.borrow_mut(), &report_context, &(), self.throughput.clone(), ); all_ids.push(id); } if let Some(conn) = &c.connection { conn.send(&OutgoingMessage::FinishedBenchmarkGroup { group: group_id }) .unwrap(); conn.serve_value_formatter(c.measurement.formatter()) .unwrap(); } if all_ids.len() > 1 && any_matched && c.mode.is_benchmark() { c.report .summarize(&report_context, &all_ids, c.measurement.formatter()); } if any_matched { c.report.group_separator(); } } } impl ParameterizedBenchmark where T: Debug + 'static, M: Measurement + 'static, { benchmark_config!(ParameterizedBenchmark); pub(crate) fn with_functions( functions: Vec>, parameters: Vec, ) -> ParameterizedBenchmark { ParameterizedBenchmark { config: PartialBenchmarkConfig::default(), values: parameters, routines: functions, throughput: None, } } /// Create a new parameterized benchmark group and adds the given function /// to it. /// The function under test must follow the setup - bench - teardown pattern: /// /// # Example /// /// ```rust /// # #[macro_use] extern crate criterion; /// # use criterion::*; /// /// fn bench(c: &mut Criterion) { /// let parameters = vec![1u64, 2u64, 3u64]; /// /// // One-time setup goes here /// c.bench( /// "my_group", /// ParameterizedBenchmark::new( /// "my_function", /// |b, param| b.iter(|| { /// // Code to benchmark using param goes here /// }), /// parameters /// ) /// ); /// } /// /// criterion_group!(benches, bench); /// criterion_main!(benches); /// ``` pub fn new(id: S, f: F, parameters: I) -> ParameterizedBenchmark where S: Into, F: FnMut(&mut Bencher<'_, M>, &T) + 'static, I: IntoIterator, { ParameterizedBenchmark { config: PartialBenchmarkConfig::default(), values: parameters.into_iter().collect(), routines: vec![], throughput: None, } .with_function(id, f) } /// Add a function to the benchmark group. pub fn with_function(mut self, id: S, f: F) -> ParameterizedBenchmark where S: Into, F: FnMut(&mut Bencher<'_, M>, &T) + 'static, { let routine = NamedRoutine { id: id.into(), f: Box::new(RefCell::new(Function::new(f))), }; self.routines.push(routine); self } /// Use the given function to calculate the input size for a given input. pub fn throughput(mut self, throughput: F) -> ParameterizedBenchmark where F: Fn(&T) -> Throughput + 'static, { self.throughput = Some(Box::new(throughput)); self } } impl BenchmarkDefinition for ParameterizedBenchmark where T: Debug + 'static, M: Measurement + 'static, { fn run(self, group_id: &str, c: &mut Criterion) { let report_context = ReportContext { output_directory: c.output_directory.clone(), plot_config: self.config.plot_config.clone(), }; let config = self.config.to_complete(&c.config); let num_parameters = self.values.len(); let num_routines = self.routines.len(); let mut all_ids = vec![]; let mut any_matched = false; if let Some(conn) = &c.connection { conn.send(&OutgoingMessage::BeginningBenchmarkGroup { group: group_id }) .unwrap(); } for routine in self.routines { for value in &self.values { let function_id = if num_routines == 1 && group_id == routine.id { None } else { Some(routine.id.clone()) }; let value_str = if num_parameters == 1 { None } else { Some(format!("{:?}", value)) }; let throughput = self.throughput.as_ref().map(|func| func(value)); let mut id = BenchmarkId::new( group_id.to_owned(), function_id, value_str, throughput.clone(), ); id.ensure_directory_name_unique(&c.all_directories); c.all_directories.insert(id.as_directory_name().to_owned()); id.ensure_title_unique(&c.all_titles); c.all_titles.insert(id.as_title().to_owned()); let do_run = c.filter_matches(id.id()); any_matched |= do_run; execute_benchmark( do_run, &id, c, &config, &mut *routine.f.borrow_mut(), &report_context, value, throughput, ); all_ids.push(id); } } if let Some(conn) = &c.connection { conn.send(&OutgoingMessage::FinishedBenchmarkGroup { group: group_id }) .unwrap(); conn.serve_value_formatter(c.measurement.formatter()) .unwrap(); } if all_ids.len() > 1 && any_matched && c.mode.is_benchmark() { c.report .summarize(&report_context, &all_ids, c.measurement.formatter()); } if any_matched { c.report.group_separator(); } } } #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] fn execute_benchmark( do_run: bool, id: &BenchmarkId, c: &Criterion, config: &BenchmarkConfig, routine: &mut dyn Routine, report_context: &ReportContext, parameter: &T, throughput: Option, ) where T: Debug, M: Measurement, { match c.mode { Mode::Benchmark => { if let Some(conn) = &c.connection { if do_run { conn.send(&OutgoingMessage::BeginningBenchmark { id: id.into() }) .unwrap(); } else { conn.send(&OutgoingMessage::SkippingBenchmark { id: id.into() }) .unwrap(); } } if do_run { analysis::common( id, routine, config, c, report_context, parameter, throughput, ); } } Mode::List => { if do_run { println!("{}: bench", id); } } Mode::Test => { if do_run { // In test mode, run the benchmark exactly once, then exit. c.report.test_start(id, report_context); routine.test(&c.measurement, parameter); c.report.test_pass(id, report_context); } } Mode::Profile(duration) => { if do_run { routine.profile(&c.measurement, id, c, report_context, duration, parameter); } } } }