use crate::analysis; use crate::benchmark::PartialBenchmarkConfig; use crate::connection::OutgoingMessage; use crate::measurement::Measurement; use crate::report::BenchmarkId as InternalBenchmarkId; use crate::report::Report; use crate::report::ReportContext; use crate::routine::{Function, Routine}; use crate::{Bencher, Criterion, DurationExt, Mode, PlotConfiguration, SamplingMode, Throughput}; use std::time::Duration; /// Structure used to group together a set of related benchmarks, along with custom configuration /// settings for groups of benchmarks. All benchmarks performed using a benchmark group will be /// grouped together in the final report. /// /// # Examples: /// /// ```no_run /// #[macro_use] extern crate criterion; /// use self::criterion::*; /// use std::time::Duration; /// /// fn bench_simple(c: &mut Criterion) { /// let mut group = c.benchmark_group("My Group"); /// /// // Now we can perform benchmarks with this group /// group.bench_function("Bench 1", |b| b.iter(|| 1 )); /// group.bench_function("Bench 2", |b| b.iter(|| 2 )); /// /// // It's recommended to call group.finish() explicitly at the end, but if you don't it will /// // be called automatically when the group is dropped. /// group.finish(); /// } /// /// fn bench_nested(c: &mut Criterion) { /// let mut group = c.benchmark_group("My Second Group"); /// // We can override the configuration on a per-group level /// group.measurement_time(Duration::from_secs(1)); /// /// // We can also use loops to define multiple benchmarks, even over multiple dimensions. /// for x in 0..3 { /// for y in 0..3 { /// let point = (x, y); /// let parameter_string = format!("{} * {}", x, y); /// group.bench_with_input(BenchmarkId::new("Multiply", parameter_string), &point, /// |b, (p_x, p_y)| b.iter(|| p_x * p_y)); /// } /// } /// /// group.finish(); /// } /// /// fn bench_throughput(c: &mut Criterion) { /// let mut group = c.benchmark_group("Summation"); /// /// for size in [1024, 2048, 4096].iter() { /// // Generate input of an appropriate size... /// let input = vec![1u64, *size]; /// /// // We can use the throughput function to tell Criterion.rs how large the input is /// // so it can calculate the overall throughput of the function. If we wanted, we could /// // even change the benchmark configuration for different inputs (eg. to reduce the /// // number of samples for extremely large and slow inputs) or even different functions. /// group.throughput(Throughput::Elements(*size as u64)); /// /// group.bench_with_input(BenchmarkId::new("sum", *size), &input, /// |b, i| b.iter(|| i.iter().sum::())); /// group.bench_with_input(BenchmarkId::new("fold", *size), &input, /// |b, i| b.iter(|| i.iter().fold(0u64, |a, b| a + b))); /// } /// /// group.finish(); /// } /// /// criterion_group!(benches, bench_simple, bench_nested, bench_throughput); /// criterion_main!(benches); /// ``` pub struct BenchmarkGroup<'a, M: Measurement> { criterion: &'a mut Criterion, group_name: String, all_ids: Vec, any_matched: bool, partial_config: PartialBenchmarkConfig, throughput: Option, } impl<'a, M: Measurement> BenchmarkGroup<'a, M> { /// 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) -> &mut Self { assert!(n >= 10); self.partial_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) -> &mut Self { assert!(dur.to_nanos() > 0); self.partial_config.warm_up_time = Some(dur); self } /// Changes the target measurement time for this benchmark group. /// /// Criterion will attempt to spent approximately this amount of time measuring each /// benchmark on a best-effort basis. If it is not possible to perform the measurement in /// the requested time (eg. because each iteration of the benchmark is long) then Criterion /// will spend as long as is needed to collect the desired number of samples. With a longer /// time, the measurement will become more resilient to interference from other programs. /// /// # Panics /// /// Panics if the input duration is zero pub fn measurement_time(&mut self, dur: Duration) -> &mut Self { assert!(dur.to_nanos() > 0); self.partial_config.measurement_time = Some(dur); self } /// Changes the number of resamples for this benchmark group /// /// 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) -> &mut Self { assert!(n > 0); if n <= 1000 { println!("\nWarning: It is not recommended to reduce nresamples below 1000."); } self.partial_config.nresamples = Some(n); self } /// Changes the noise threshold for benchmarks in this group. The noise threshold /// is used to filter out small changes in performance from one run to the next, 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) -> &mut Self { assert!(threshold >= 0.0); self.partial_config.noise_threshold = Some(threshold); self } /// Changes the confidence level for benchmarks in this group. 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) -> &mut 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.partial_config.confidence_level = Some(cl); self } /// Changes the [significance level](https://en.wikipedia.org/wiki/Statistical_significance) /// for benchmarks in this group. 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) -> &mut Self { assert!(sl > 0.0 && sl < 1.0); self.partial_config.significance_level = Some(sl); self } /// Changes the plot configuration for this benchmark group. pub fn plot_config(&mut self, new_config: PlotConfiguration) -> &mut Self { self.partial_config.plot_config = new_config; self } /// Set the input size for this benchmark group. Used for reporting the /// throughput. pub fn throughput(&mut self, throughput: Throughput) -> &mut Self { self.throughput = Some(throughput); self } /// Set the sampling mode for this benchmark group. pub fn sampling_mode(&mut self, new_mode: SamplingMode) -> &mut Self { self.partial_config.sampling_mode = Some(new_mode); self } pub(crate) fn new(criterion: &mut Criterion, group_name: String) -> BenchmarkGroup<'_, M> { BenchmarkGroup { criterion, group_name, all_ids: vec![], any_matched: false, partial_config: PartialBenchmarkConfig::default(), throughput: None, } } /// Benchmark the given parameterless function inside this benchmark group. pub fn bench_function(&mut self, id: ID, mut f: F) -> &mut Self where F: FnMut(&mut Bencher<'_, M>), { self.run_bench(id.into_benchmark_id(), &(), |b, _| f(b)); self } /// Benchmark the given parameterized function inside this benchmark group. pub fn bench_with_input( &mut self, id: ID, input: &I, f: F, ) -> &mut Self where F: FnMut(&mut Bencher<'_, M>, &I), I: ?Sized, { self.run_bench(id.into_benchmark_id(), input, f); self } fn run_bench(&mut self, id: BenchmarkId, input: &I, f: F) where F: FnMut(&mut Bencher<'_, M>, &I), I: ?Sized, { let config = self.partial_config.to_complete(&self.criterion.config); let report_context = ReportContext { output_directory: self.criterion.output_directory.clone(), plot_config: self.partial_config.plot_config.clone(), }; let mut id = InternalBenchmarkId::new( self.group_name.clone(), id.function_name, id.parameter, self.throughput.clone(), ); assert!( !self.all_ids.contains(&id), "Benchmark IDs must be unique within a group." ); id.ensure_directory_name_unique(&self.criterion.all_directories); self.criterion .all_directories .insert(id.as_directory_name().to_owned()); id.ensure_title_unique(&self.criterion.all_titles); self.criterion.all_titles.insert(id.as_title().to_owned()); let do_run = self.criterion.filter_matches(id.id()); self.any_matched |= do_run; let mut func = Function::new(f); match self.criterion.mode { Mode::Benchmark => { if let Some(conn) = &self.criterion.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, &mut func, &config, self.criterion, &report_context, input, self.throughput.clone(), ); } } Mode::List => { if do_run { println!("{}: bench", id); } } Mode::Test => { if do_run { // In test mode, run the benchmark exactly once, then exit. self.criterion.report.test_start(&id, &report_context); func.test(&self.criterion.measurement, input); self.criterion.report.test_pass(&id, &report_context); } } Mode::Profile(duration) => { if do_run { func.profile( &self.criterion.measurement, &id, &self.criterion, &report_context, duration, input, ); } } } self.all_ids.push(id); } /// Consume the benchmark group and generate the summary reports for the group. /// /// It is recommended to call this explicitly, but if you forget it will be called when the /// group is dropped. pub fn finish(self) { ::std::mem::drop(self); } } impl<'a, M: Measurement> Drop for BenchmarkGroup<'a, M> { fn drop(&mut self) { // I don't really like having a bunch of non-trivial code in drop, but this is the only way // to really write linear types like this in Rust... if let Some(conn) = &mut self.criterion.connection { conn.send(&OutgoingMessage::FinishedBenchmarkGroup { group: &self.group_name, }) .unwrap(); conn.serve_value_formatter(self.criterion.measurement.formatter()) .unwrap(); } if self.all_ids.len() > 1 && self.any_matched && self.criterion.mode.is_benchmark() { let report_context = ReportContext { output_directory: self.criterion.output_directory.clone(), plot_config: self.partial_config.plot_config.clone(), }; self.criterion.report.summarize( &report_context, &self.all_ids, self.criterion.measurement.formatter(), ); } if self.any_matched { self.criterion.report.group_separator(); } } } /// Simple structure representing an ID for a benchmark. The ID must be unique within a benchmark /// group. #[derive(Clone, Eq, PartialEq, Hash)] pub struct BenchmarkId { pub(crate) function_name: Option, pub(crate) parameter: Option, } impl BenchmarkId { /// Construct a new benchmark ID from a string function name and a parameter value. /// /// Note that the parameter value need not be the same as the parameter passed to your /// actual benchmark. For instance, you might have a benchmark that takes a 1MB string as /// input. It would be impractical to embed the whole string in the benchmark ID, so instead /// your parameter value might be a descriptive string like "1MB Alphanumeric". /// /// # Examples /// ``` /// # use criterion::{BenchmarkId, Criterion}; /// // A basic benchmark ID is typically constructed from a constant string and a simple /// // parameter /// let basic_id = BenchmarkId::new("my_id", 5); /// /// // The function name can be a string /// let function_name = "test_string".to_string(); /// let string_id = BenchmarkId::new(function_name, 12); /// /// // Benchmark IDs are passed to benchmark groups: /// let mut criterion = Criterion::default(); /// let mut group = criterion.benchmark_group("My Group"); /// // Generate a very large input /// let input : String = ::std::iter::repeat("X").take(1024 * 1024).collect(); /// /// // Note that we don't have to use the input as the parameter in the ID /// group.bench_with_input(BenchmarkId::new("Test long string", "1MB X's"), &input, |b, i| { /// b.iter(|| i.len()) /// }); /// ``` pub fn new, P: ::std::fmt::Display>( function_name: S, parameter: P, ) -> BenchmarkId { BenchmarkId { function_name: Some(function_name.into()), parameter: Some(format!("{}", parameter)), } } /// Construct a new benchmark ID from just a parameter value. Use this when benchmarking a /// single function with a variety of different inputs. pub fn from_parameter(parameter: P) -> BenchmarkId { BenchmarkId { function_name: None, parameter: Some(format!("{}", parameter)), } } pub(crate) fn no_function() -> BenchmarkId { BenchmarkId { function_name: None, parameter: None, } } pub(crate) fn no_function_with_input(parameter: P) -> BenchmarkId { BenchmarkId { function_name: None, parameter: Some(format!("{}", parameter)), } } } mod private { pub trait Sealed {} impl Sealed for super::BenchmarkId {} impl> Sealed for S {} } /// Sealed trait which allows users to automatically convert strings to benchmark IDs. pub trait IntoBenchmarkId: private::Sealed { fn into_benchmark_id(self) -> BenchmarkId; } impl IntoBenchmarkId for BenchmarkId { fn into_benchmark_id(self) -> BenchmarkId { self } } impl> IntoBenchmarkId for S { fn into_benchmark_id(self) -> BenchmarkId { let function_name = self.into(); if function_name.is_empty() { panic!("Function name must not be empty."); } BenchmarkId { function_name: Some(function_name), parameter: None, } } }