1 //! A statistics-driven micro-benchmarking library written in Rust.
2 //!
3 //! This crate is a microbenchmarking library which aims to provide strong
4 //! statistical confidence in detecting and estimating the size of performance
5 //! improvements and regressions, while also being easy to use.
6 //!
7 //! See
8 //! [the user guide](https://bheisler.github.io/criterion.rs/book/index.html)
9 //! for examples as well as details on the measurement and analysis process,
10 //! and the output.
11 //!
12 //! ## Features:
13 //! * Collects detailed statistics, providing strong confidence that changes
14 //!   to performance are real, not measurement noise.
15 //! * Produces detailed charts, providing thorough understanding of your code's
16 //!   performance behavior.
17 
18 #![warn(missing_docs)]
19 #![warn(bare_trait_objects)]
20 #![cfg_attr(feature = "real_blackbox", feature(test))]
21 #![cfg_attr(
22     feature = "cargo-clippy",
23     allow(
24         clippy::just_underscores_and_digits, // Used in the stats code
25         clippy::transmute_ptr_to_ptr, // Used in the stats code
26         clippy::option_as_ref_deref, // Remove when MSRV bumped above 1.40
27         clippy::manual_non_exhaustive, // Remove when MSRV bumped above 1.40
28         clippy::match_like_matches_macro, // Remove when MSRV bumped above 1.42
29     )
30 )]
31 
32 #[cfg(test)]
33 extern crate approx;
34 
35 #[cfg(test)]
36 extern crate quickcheck;
37 
38 use clap::value_t;
39 use regex::Regex;
40 
41 #[macro_use]
42 extern crate lazy_static;
43 
44 #[cfg(feature = "real_blackbox")]
45 extern crate test;
46 
47 #[macro_use]
48 extern crate serde_derive;
49 
50 // Needs to be declared before other modules
51 // in order to be usable there.
52 #[macro_use]
53 mod macros_private;
54 #[macro_use]
55 mod analysis;
56 mod benchmark;
57 #[macro_use]
58 mod benchmark_group;
59 pub mod async_executor;
60 mod bencher;
61 mod connection;
62 mod csv_report;
63 mod error;
64 mod estimate;
65 mod format;
66 mod fs;
67 mod html;
68 mod kde;
69 mod macros;
70 pub mod measurement;
71 mod plot;
72 pub mod profiler;
73 mod report;
74 mod routine;
75 mod stats;
76 
77 use std::cell::RefCell;
78 use std::collections::HashSet;
79 use std::default::Default;
80 use std::env;
81 use std::fmt;
82 use std::iter::IntoIterator;
83 use std::marker::PhantomData;
84 use std::net::TcpStream;
85 use std::path::{Path, PathBuf};
86 use std::process::Command;
87 use std::sync::{Mutex, MutexGuard};
88 use std::time::Duration;
89 
90 use criterion_plot::{Version, VersionError};
91 
92 use crate::benchmark::BenchmarkConfig;
93 use crate::benchmark::NamedRoutine;
94 use crate::connection::Connection;
95 use crate::connection::OutgoingMessage;
96 use crate::csv_report::FileCsvReport;
97 use crate::html::Html;
98 use crate::measurement::{Measurement, WallTime};
99 use crate::plot::{Gnuplot, Plotter, PlottersBackend};
100 use crate::profiler::{ExternalProfiler, Profiler};
101 use crate::report::{BencherReport, CliReport, Report, ReportContext, Reports};
102 use crate::routine::Function;
103 
104 #[cfg(feature = "async")]
105 pub use crate::bencher::AsyncBencher;
106 pub use crate::bencher::Bencher;
107 #[allow(deprecated)]
108 pub use crate::benchmark::{Benchmark, BenchmarkDefinition, ParameterizedBenchmark};
109 pub use crate::benchmark_group::{BenchmarkGroup, BenchmarkId};
110 
111 lazy_static! {
112     static ref DEBUG_ENABLED: bool = std::env::var_os("CRITERION_DEBUG").is_some();
113     static ref GNUPLOT_VERSION: Result<Version, VersionError> = criterion_plot::version();
114     static ref DEFAULT_PLOTTING_BACKEND: PlottingBackend = {
115         match &*GNUPLOT_VERSION {
116             Ok(_) => PlottingBackend::Gnuplot,
117             Err(e) => {
118                 match e {
119                     VersionError::Exec(_) => println!("Gnuplot not found, using plotters backend"),
120                     e => println!(
121                         "Gnuplot not found or not usable, using plotters backend\n{}",
122                         e
123                     ),
124                 };
125                 PlottingBackend::Plotters
126             }
127         }
128     };
129     static ref CARGO_CRITERION_CONNECTION: Option<Mutex<Connection>> = {
130         match std::env::var("CARGO_CRITERION_PORT") {
131             Ok(port_str) => {
132                 let port: u16 = port_str.parse().ok()?;
133                 let stream = TcpStream::connect(("localhost", port)).ok()?;
134                 Some(Mutex::new(Connection::new(stream).ok()?))
135             }
136             Err(_) => None,
137         }
138     };
139     static ref DEFAULT_OUTPUT_DIRECTORY: PathBuf = {
140         // Set criterion home to (in descending order of preference):
141         // - $CRITERION_HOME (cargo-criterion sets this, but other users could as well)
142         // - $CARGO_TARGET_DIR/criterion
143         // - the cargo target dir from `cargo metadata`
144         // - ./target/criterion
145         if let Some(value) = env::var_os("CRITERION_HOME") {
146             PathBuf::from(value)
147         } else if let Some(path) = cargo_target_directory() {
148             path.join("criterion")
149         } else {
150             PathBuf::from("target/criterion")
151         }
152     };
153 }
154 
debug_enabled() -> bool155 fn debug_enabled() -> bool {
156     *DEBUG_ENABLED
157 }
158 
159 /// A function that is opaque to the optimizer, used to prevent the compiler from
160 /// optimizing away computations in a benchmark.
161 ///
162 /// This variant is backed by the (unstable) test::black_box function.
163 #[cfg(feature = "real_blackbox")]
black_box<T>(dummy: T) -> T164 pub fn black_box<T>(dummy: T) -> T {
165     test::black_box(dummy)
166 }
167 
168 /// A function that is opaque to the optimizer, used to prevent the compiler from
169 /// optimizing away computations in a benchmark.
170 ///
171 /// This variant is stable-compatible, but it may cause some performance overhead
172 /// or fail to prevent code from being eliminated.
173 #[cfg(not(feature = "real_blackbox"))]
black_box<T>(dummy: T) -> T174 pub fn black_box<T>(dummy: T) -> T {
175     unsafe {
176         let ret = std::ptr::read_volatile(&dummy);
177         std::mem::forget(dummy);
178         ret
179     }
180 }
181 
182 /// Representing a function to benchmark together with a name of that function.
183 /// Used together with `bench_functions` to represent one out of multiple functions
184 /// under benchmark.
185 #[doc(hidden)]
186 pub struct Fun<I: fmt::Debug, M: Measurement + 'static = WallTime> {
187     f: NamedRoutine<I, M>,
188     _phantom: PhantomData<M>,
189 }
190 
191 impl<I, M: Measurement> Fun<I, M>
192 where
193     I: fmt::Debug + 'static,
194 {
195     /// Create a new `Fun` given a name and a closure
new<F>(name: &str, f: F) -> Fun<I, M> where F: FnMut(&mut Bencher<'_, M>, &I) + 'static,196     pub fn new<F>(name: &str, f: F) -> Fun<I, M>
197     where
198         F: FnMut(&mut Bencher<'_, M>, &I) + 'static,
199     {
200         let routine = NamedRoutine {
201             id: name.to_owned(),
202             f: Box::new(RefCell::new(Function::new(f))),
203         };
204 
205         Fun {
206             f: routine,
207             _phantom: PhantomData,
208         }
209     }
210 }
211 
212 /// Argument to [`Bencher::iter_batched`](struct.Bencher.html#method.iter_batched) and
213 /// [`Bencher::iter_batched_ref`](struct.Bencher.html#method.iter_batched_ref) which controls the
214 /// batch size.
215 ///
216 /// Generally speaking, almost all benchmarks should use `SmallInput`. If the input or the result
217 /// of the benchmark routine is large enough that `SmallInput` causes out-of-memory errors,
218 /// `LargeInput` can be used to reduce memory usage at the cost of increasing the measurement
219 /// overhead. If the input or the result is extremely large (or if it holds some
220 /// limited external resource like a file handle), `PerIteration` will set the number of iterations
221 /// per batch to exactly one. `PerIteration` can increase the measurement overhead substantially
222 /// and should be avoided wherever possible.
223 ///
224 /// Each value lists an estimate of the measurement overhead. This is intended as a rough guide
225 /// to assist in choosing an option, it should not be relied upon. In particular, it is not valid
226 /// to subtract the listed overhead from the measurement and assume that the result represents the
227 /// true runtime of a function. The actual measurement overhead for your specific benchmark depends
228 /// on the details of the function you're benchmarking and the hardware and operating
229 /// system running the benchmark.
230 ///
231 /// With that said, if the runtime of your function is small relative to the measurement overhead
232 /// it will be difficult to take accurate measurements. In this situation, the best option is to use
233 /// [`Bencher::iter`](struct.Bencher.html#method.iter) which has next-to-zero measurement overhead.
234 #[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
235 pub enum BatchSize {
236     /// `SmallInput` indicates that the input to the benchmark routine (the value returned from
237     /// the setup routine) is small enough that millions of values can be safely held in memory.
238     /// Always prefer `SmallInput` unless the benchmark is using too much memory.
239     ///
240     /// In testing, the maximum measurement overhead from benchmarking with `SmallInput` is on the
241     /// order of 500 picoseconds. This is presented as a rough guide; your results may vary.
242     SmallInput,
243 
244     /// `LargeInput` indicates that the input to the benchmark routine or the value returned from
245     /// that routine is large. This will reduce the memory usage but increase the measurement
246     /// overhead.
247     ///
248     /// In testing, the maximum measurement overhead from benchmarking with `LargeInput` is on the
249     /// order of 750 picoseconds. This is presented as a rough guide; your results may vary.
250     LargeInput,
251 
252     /// `PerIteration` indicates that the input to the benchmark routine or the value returned from
253     /// that routine is extremely large or holds some limited resource, such that holding many values
254     /// in memory at once is infeasible. This provides the worst measurement overhead, but the
255     /// lowest memory usage.
256     ///
257     /// In testing, the maximum measurement overhead from benchmarking with `PerIteration` is on the
258     /// order of 350 nanoseconds or 350,000 picoseconds. This is presented as a rough guide; your
259     /// results may vary.
260     PerIteration,
261 
262     /// `NumBatches` will attempt to divide the iterations up into a given number of batches.
263     /// A larger number of batches (and thus smaller batches) will reduce memory usage but increase
264     /// measurement overhead. This allows the user to choose their own tradeoff between memory usage
265     /// and measurement overhead, but care must be taken in tuning the number of batches. Most
266     /// benchmarks should use `SmallInput` or `LargeInput` instead.
267     NumBatches(u64),
268 
269     /// `NumIterations` fixes the batch size to a constant number, specified by the user. This
270     /// allows the user to choose their own tradeoff between overhead and memory usage, but care must
271     /// be taken in tuning the batch size. In general, the measurement overhead of `NumIterations`
272     /// will be larger than that of `NumBatches`. Most benchmarks should use `SmallInput` or
273     /// `LargeInput` instead.
274     NumIterations(u64),
275 
276     #[doc(hidden)]
277     __NonExhaustive,
278 }
279 impl BatchSize {
280     /// Convert to a number of iterations per batch.
281     ///
282     /// We try to do a constant number of batches regardless of the number of iterations in this
283     /// sample. If the measurement overhead is roughly constant regardless of the number of
284     /// iterations the analysis of the results later will have an easier time separating the
285     /// measurement overhead from the benchmark time.
iters_per_batch(self, iters: u64) -> u64286     fn iters_per_batch(self, iters: u64) -> u64 {
287         match self {
288             BatchSize::SmallInput => (iters + 10 - 1) / 10,
289             BatchSize::LargeInput => (iters + 1000 - 1) / 1000,
290             BatchSize::PerIteration => 1,
291             BatchSize::NumBatches(batches) => (iters + batches - 1) / batches,
292             BatchSize::NumIterations(size) => size,
293             BatchSize::__NonExhaustive => panic!("__NonExhaustive is not a valid BatchSize."),
294         }
295     }
296 }
297 
298 /// Baseline describes how the baseline_directory is handled.
299 #[derive(Debug, Clone, Copy)]
300 pub enum Baseline {
301     /// Compare ensures a previous saved version of the baseline
302     /// exists and runs comparison against that.
303     Compare,
304     /// Save writes the benchmark results to the baseline directory,
305     /// overwriting any results that were previously there.
306     Save,
307 }
308 
309 /// Enum used to select the plotting backend.
310 #[derive(Debug, Clone, Copy)]
311 pub enum PlottingBackend {
312     /// Plotting backend which uses the external `gnuplot` command to render plots. This is the
313     /// default if the `gnuplot` command is installed.
314     Gnuplot,
315     /// Plotting backend which uses the rust 'Plotters' library. This is the default if `gnuplot`
316     /// is not installed.
317     Plotters,
318 }
319 impl PlottingBackend {
create_plotter(&self) -> Box<dyn Plotter>320     fn create_plotter(&self) -> Box<dyn Plotter> {
321         match self {
322             PlottingBackend::Gnuplot => Box::new(Gnuplot::default()),
323             PlottingBackend::Plotters => Box::new(PlottersBackend::default()),
324         }
325     }
326 }
327 
328 #[derive(Debug, Clone)]
329 /// Enum representing the execution mode.
330 pub(crate) enum Mode {
331     /// Run benchmarks normally.
332     Benchmark,
333     /// List all benchmarks but do not run them.
334     List,
335     /// Run benchmarks once to verify that they work, but otherwise do not measure them.
336     Test,
337     /// Iterate benchmarks for a given length of time but do not analyze or report on them.
338     Profile(Duration),
339 }
340 impl Mode {
is_benchmark(&self) -> bool341     pub fn is_benchmark(&self) -> bool {
342         match self {
343             Mode::Benchmark => true,
344             _ => false,
345         }
346     }
347 }
348 
349 /// The benchmark manager
350 ///
351 /// `Criterion` lets you configure and execute benchmarks
352 ///
353 /// Each benchmark consists of four phases:
354 ///
355 /// - **Warm-up**: The routine is repeatedly executed, to let the CPU/OS/JIT/interpreter adapt to
356 /// the new load
357 /// - **Measurement**: The routine is repeatedly executed, and timing information is collected into
358 /// a sample
359 /// - **Analysis**: The sample is analyzed and distilled into meaningful statistics that get
360 /// reported to stdout, stored in files, and plotted
361 /// - **Comparison**: The current sample is compared with the sample obtained in the previous
362 /// benchmark.
363 pub struct Criterion<M: Measurement = WallTime> {
364     config: BenchmarkConfig,
365     filter: Option<Regex>,
366     report: Reports,
367     output_directory: PathBuf,
368     baseline_directory: String,
369     baseline: Baseline,
370     load_baseline: Option<String>,
371     all_directories: HashSet<String>,
372     all_titles: HashSet<String>,
373     measurement: M,
374     profiler: Box<RefCell<dyn Profiler>>,
375     connection: Option<MutexGuard<'static, Connection>>,
376     mode: Mode,
377 }
378 
379 /// Returns the Cargo target directory, possibly calling `cargo metadata` to
380 /// figure it out.
cargo_target_directory() -> Option<PathBuf>381 fn cargo_target_directory() -> Option<PathBuf> {
382     #[derive(Deserialize)]
383     struct Metadata {
384         target_directory: PathBuf,
385     }
386 
387     env::var_os("CARGO_TARGET_DIR")
388         .map(PathBuf::from)
389         .or_else(|| {
390             let output = Command::new(env::var_os("CARGO")?)
391                 .args(&["metadata", "--format-version", "1"])
392                 .output()
393                 .ok()?;
394             let metadata: Metadata = serde_json::from_slice(&output.stdout).ok()?;
395             Some(metadata.target_directory)
396         })
397 }
398 
399 impl Default for Criterion {
400     /// Creates a benchmark manager with the following default settings:
401     ///
402     /// - Sample size: 100 measurements
403     /// - Warm-up time: 3 s
404     /// - Measurement time: 5 s
405     /// - Bootstrap size: 100 000 resamples
406     /// - Noise threshold: 0.01 (1%)
407     /// - Confidence level: 0.95
408     /// - Significance level: 0.05
409     /// - Plotting: enabled, using gnuplot if available or plotters if gnuplot is not available
410     /// - No filter
default() -> Criterion411     fn default() -> Criterion {
412         let reports = Reports {
413             cli_enabled: true,
414             cli: CliReport::new(false, false, false),
415             bencher_enabled: false,
416             bencher: BencherReport,
417             html_enabled: true,
418             html: Html::new(DEFAULT_PLOTTING_BACKEND.create_plotter()),
419             csv_enabled: true,
420             csv: FileCsvReport,
421         };
422 
423         let mut criterion = Criterion {
424             config: BenchmarkConfig {
425                 confidence_level: 0.95,
426                 measurement_time: Duration::new(5, 0),
427                 noise_threshold: 0.01,
428                 nresamples: 100_000,
429                 sample_size: 100,
430                 significance_level: 0.05,
431                 warm_up_time: Duration::new(3, 0),
432                 sampling_mode: SamplingMode::Auto,
433             },
434             filter: None,
435             report: reports,
436             baseline_directory: "base".to_owned(),
437             baseline: Baseline::Save,
438             load_baseline: None,
439             output_directory: DEFAULT_OUTPUT_DIRECTORY.clone(),
440             all_directories: HashSet::new(),
441             all_titles: HashSet::new(),
442             measurement: WallTime,
443             profiler: Box::new(RefCell::new(ExternalProfiler)),
444             connection: CARGO_CRITERION_CONNECTION
445                 .as_ref()
446                 .map(|mtx| mtx.lock().unwrap()),
447             mode: Mode::Benchmark,
448         };
449 
450         if criterion.connection.is_some() {
451             // disable all reports when connected to cargo-criterion; it will do the reporting.
452             criterion.report.cli_enabled = false;
453             criterion.report.bencher_enabled = false;
454             criterion.report.csv_enabled = false;
455             criterion.report.html_enabled = false;
456         }
457         criterion
458     }
459 }
460 
461 impl<M: Measurement> Criterion<M> {
462     /// Changes the measurement for the benchmarks run with this runner. See the
463     /// Measurement trait for more details
with_measurement<M2: Measurement>(self, m: M2) -> Criterion<M2>464     pub fn with_measurement<M2: Measurement>(self, m: M2) -> Criterion<M2> {
465         // Can't use struct update syntax here because they're technically different types.
466         Criterion {
467             config: self.config,
468             filter: self.filter,
469             report: self.report,
470             baseline_directory: self.baseline_directory,
471             baseline: self.baseline,
472             load_baseline: self.load_baseline,
473             output_directory: self.output_directory,
474             all_directories: self.all_directories,
475             all_titles: self.all_titles,
476             measurement: m,
477             profiler: self.profiler,
478             connection: self.connection,
479             mode: self.mode,
480         }
481     }
482 
483     /// Changes the internal profiler for benchmarks run with this runner. See
484     /// the Profiler trait for more details.
with_profiler<P: Profiler + 'static>(self, p: P) -> Criterion<M>485     pub fn with_profiler<P: Profiler + 'static>(self, p: P) -> Criterion<M> {
486         Criterion {
487             profiler: Box::new(RefCell::new(p)),
488             ..self
489         }
490     }
491 
492     /// Set the plotting backend. By default, Criterion will use gnuplot if available, or plotters
493     /// if not.
494     ///
495     /// Panics if `backend` is `PlottingBackend::Gnuplot` and gnuplot is not available.
plotting_backend(mut self, backend: PlottingBackend) -> Criterion<M>496     pub fn plotting_backend(mut self, backend: PlottingBackend) -> Criterion<M> {
497         if let PlottingBackend::Gnuplot = backend {
498             if GNUPLOT_VERSION.is_err() {
499                 panic!("Gnuplot plotting backend was requested, but gnuplot is not available. To continue, either install Gnuplot or allow Criterion.rs to fall back to using plotters.");
500             }
501         }
502 
503         self.report.html = Html::new(backend.create_plotter());
504         self
505     }
506 
507     /// Changes the default size of the sample for benchmarks run with this runner.
508     ///
509     /// A bigger sample should yield more accurate results if paired with a sufficiently large
510     /// measurement time.
511     ///
512     /// Sample size must be at least 10.
513     ///
514     /// # Panics
515     ///
516     /// Panics if n < 10
sample_size(mut self, n: usize) -> Criterion<M>517     pub fn sample_size(mut self, n: usize) -> Criterion<M> {
518         assert!(n >= 10);
519 
520         self.config.sample_size = n;
521         self
522     }
523 
524     /// Changes the default warm up time for benchmarks run with this runner.
525     ///
526     /// # Panics
527     ///
528     /// Panics if the input duration is zero
warm_up_time(mut self, dur: Duration) -> Criterion<M>529     pub fn warm_up_time(mut self, dur: Duration) -> Criterion<M> {
530         assert!(dur.to_nanos() > 0);
531 
532         self.config.warm_up_time = dur;
533         self
534     }
535 
536     /// Changes the default measurement time for benchmarks run with this runner.
537     ///
538     /// With a longer time, the measurement will become more resilient to transitory peak loads
539     /// caused by external programs
540     ///
541     /// **Note**: If the measurement time is too "low", Criterion will automatically increase it
542     ///
543     /// # Panics
544     ///
545     /// Panics if the input duration in zero
measurement_time(mut self, dur: Duration) -> Criterion<M>546     pub fn measurement_time(mut self, dur: Duration) -> Criterion<M> {
547         assert!(dur.to_nanos() > 0);
548 
549         self.config.measurement_time = dur;
550         self
551     }
552 
553     /// Changes the default number of resamples for benchmarks run with this runner.
554     ///
555     /// Number of resamples to use for the
556     /// [bootstrap](http://en.wikipedia.org/wiki/Bootstrapping_(statistics)#Case_resampling)
557     ///
558     /// A larger number of resamples reduces the random sampling errors, which are inherent to the
559     /// bootstrap method, but also increases the analysis time
560     ///
561     /// # Panics
562     ///
563     /// Panics if the number of resamples is set to zero
nresamples(mut self, n: usize) -> Criterion<M>564     pub fn nresamples(mut self, n: usize) -> Criterion<M> {
565         assert!(n > 0);
566         if n <= 1000 {
567             println!("\nWarning: It is not recommended to reduce nresamples below 1000.");
568         }
569 
570         self.config.nresamples = n;
571         self
572     }
573 
574     /// Changes the default noise threshold for benchmarks run with this runner. The noise threshold
575     /// is used to filter out small changes in performance, even if they are statistically
576     /// significant. Sometimes benchmarking the same code twice will result in small but
577     /// statistically significant differences solely because of noise. This provides a way to filter
578     /// out some of these false positives at the cost of making it harder to detect small changes
579     /// to the true performance of the benchmark.
580     ///
581     /// The default is 0.01, meaning that changes smaller than 1% will be ignored.
582     ///
583     /// # Panics
584     ///
585     /// Panics if the threshold is set to a negative value
noise_threshold(mut self, threshold: f64) -> Criterion<M>586     pub fn noise_threshold(mut self, threshold: f64) -> Criterion<M> {
587         assert!(threshold >= 0.0);
588 
589         self.config.noise_threshold = threshold;
590         self
591     }
592 
593     /// Changes the default confidence level for benchmarks run with this runner. The confidence
594     /// level is the desired probability that the true runtime lies within the estimated
595     /// [confidence interval](https://en.wikipedia.org/wiki/Confidence_interval). The default is
596     /// 0.95, meaning that the confidence interval should capture the true value 95% of the time.
597     ///
598     /// # Panics
599     ///
600     /// Panics if the confidence level is set to a value outside the `(0, 1)` range
confidence_level(mut self, cl: f64) -> Criterion<M>601     pub fn confidence_level(mut self, cl: f64) -> Criterion<M> {
602         assert!(cl > 0.0 && cl < 1.0);
603         if cl < 0.5 {
604             println!("\nWarning: It is not recommended to reduce confidence level below 0.5.");
605         }
606 
607         self.config.confidence_level = cl;
608         self
609     }
610 
611     /// Changes the default [significance level](https://en.wikipedia.org/wiki/Statistical_significance)
612     /// for benchmarks run with this runner. This is used to perform a
613     /// [hypothesis test](https://en.wikipedia.org/wiki/Statistical_hypothesis_testing) to see if
614     /// the measurements from this run are different from the measured performance of the last run.
615     /// The significance level is the desired probability that two measurements of identical code
616     /// will be considered 'different' due to noise in the measurements. The default value is 0.05,
617     /// meaning that approximately 5% of identical benchmarks will register as different due to
618     /// noise.
619     ///
620     /// This presents a trade-off. By setting the significance level closer to 0.0, you can increase
621     /// the statistical robustness against noise, but it also weakens Criterion.rs' ability to
622     /// detect small but real changes in the performance. By setting the significance level
623     /// closer to 1.0, Criterion.rs will be more able to detect small true changes, but will also
624     /// report more spurious differences.
625     ///
626     /// See also the noise threshold setting.
627     ///
628     /// # Panics
629     ///
630     /// Panics if the significance level is set to a value outside the `(0, 1)` range
significance_level(mut self, sl: f64) -> Criterion<M>631     pub fn significance_level(mut self, sl: f64) -> Criterion<M> {
632         assert!(sl > 0.0 && sl < 1.0);
633 
634         self.config.significance_level = sl;
635         self
636     }
637 
638     /// Enables plotting
with_plots(mut self) -> Criterion<M>639     pub fn with_plots(mut self) -> Criterion<M> {
640         // If running under cargo-criterion then don't re-enable the reports; let it do the reporting.
641         if self.connection.is_none() {
642             self.report.html_enabled = true;
643         }
644         self
645     }
646 
647     /// Disables plotting
without_plots(mut self) -> Criterion<M>648     pub fn without_plots(mut self) -> Criterion<M> {
649         self.report.html_enabled = false;
650         self
651     }
652 
653     /// Return true if generation of the plots is possible.
654     #[deprecated(
655         since = "0.3.4",
656         note = "No longer useful; since the plotters backend is available Criterion.rs can always generate plots"
657     )]
can_plot(&self) -> bool658     pub fn can_plot(&self) -> bool {
659         // Trivially true now that we have plotters.
660         // TODO: Deprecate and remove this.
661         true
662     }
663 
664     /// Names an explicit baseline and enables overwriting the previous results.
save_baseline(mut self, baseline: String) -> Criterion<M>665     pub fn save_baseline(mut self, baseline: String) -> Criterion<M> {
666         self.baseline_directory = baseline;
667         self.baseline = Baseline::Save;
668         self
669     }
670 
671     /// Names an explicit baseline and disables overwriting the previous results.
retain_baseline(mut self, baseline: String) -> Criterion<M>672     pub fn retain_baseline(mut self, baseline: String) -> Criterion<M> {
673         self.baseline_directory = baseline;
674         self.baseline = Baseline::Compare;
675         self
676     }
677 
678     /// Filters the benchmarks. Only benchmarks with names that contain the
679     /// given string will be executed.
with_filter<S: Into<String>>(mut self, filter: S) -> Criterion<M>680     pub fn with_filter<S: Into<String>>(mut self, filter: S) -> Criterion<M> {
681         let filter_text = filter.into();
682         let filter = Regex::new(&filter_text).unwrap_or_else(|err| {
683             panic!(
684                 "Unable to parse '{}' as a regular expression: {}",
685                 filter_text, err
686             )
687         });
688         self.filter = Some(filter);
689 
690         self
691     }
692 
693     /// Override whether the CLI output will be colored or not. Usually you would use the `--color`
694     /// CLI argument, but this is available for programmmatic use as well.
with_output_color(mut self, enabled: bool) -> Criterion<M>695     pub fn with_output_color(mut self, enabled: bool) -> Criterion<M> {
696         self.report.cli.enable_text_coloring = enabled;
697         self
698     }
699 
700     /// Set the output directory (currently for testing only)
701     #[doc(hidden)]
output_directory(mut self, path: &Path) -> Criterion<M>702     pub fn output_directory(mut self, path: &Path) -> Criterion<M> {
703         self.output_directory = path.to_owned();
704 
705         self
706     }
707 
708     /// Set the profile time (currently for testing only)
709     #[doc(hidden)]
profile_time(mut self, profile_time: Option<Duration>) -> Criterion<M>710     pub fn profile_time(mut self, profile_time: Option<Duration>) -> Criterion<M> {
711         match profile_time {
712             Some(time) => self.mode = Mode::Profile(time),
713             None => self.mode = Mode::Benchmark,
714         }
715 
716         self
717     }
718 
719     /// Generate the final summary at the end of a run.
720     #[doc(hidden)]
final_summary(&self)721     pub fn final_summary(&self) {
722         if !self.mode.is_benchmark() {
723             return;
724         }
725 
726         let report_context = ReportContext {
727             output_directory: self.output_directory.clone(),
728             plot_config: PlotConfiguration::default(),
729         };
730 
731         self.report.final_summary(&report_context);
732     }
733 
734     /// Configure this criterion struct based on the command-line arguments to
735     /// this process.
736     #[cfg_attr(feature = "cargo-clippy", allow(clippy::cognitive_complexity))]
configure_from_args(mut self) -> Criterion<M>737     pub fn configure_from_args(mut self) -> Criterion<M> {
738         use clap::{App, Arg};
739         let matches = App::new("Criterion Benchmark")
740             .arg(Arg::with_name("FILTER")
741                 .help("Skip benchmarks whose names do not contain FILTER.")
742                 .index(1))
743             .arg(Arg::with_name("color")
744                 .short("c")
745                 .long("color")
746                 .alias("colour")
747                 .takes_value(true)
748                 .possible_values(&["auto", "always", "never"])
749                 .default_value("auto")
750                 .help("Configure coloring of output. always = always colorize output, never = never colorize output, auto = colorize output if output is a tty and compiled for unix."))
751             .arg(Arg::with_name("verbose")
752                 .short("v")
753                 .long("verbose")
754                 .help("Print additional statistical information."))
755             .arg(Arg::with_name("noplot")
756                 .short("n")
757                 .long("noplot")
758                 .help("Disable plot and HTML generation."))
759             .arg(Arg::with_name("save-baseline")
760                 .short("s")
761                 .long("save-baseline")
762                 .default_value("base")
763                 .help("Save results under a named baseline."))
764             .arg(Arg::with_name("baseline")
765                 .short("b")
766                 .long("baseline")
767                 .takes_value(true)
768                 .conflicts_with("save-baseline")
769                 .help("Compare to a named baseline."))
770             .arg(Arg::with_name("list")
771                 .long("list")
772                 .help("List all benchmarks")
773                 .conflicts_with_all(&["test", "profile-time"]))
774             .arg(Arg::with_name("profile-time")
775                 .long("profile-time")
776                 .takes_value(true)
777                 .help("Iterate each benchmark for approximately the given number of seconds, doing no analysis and without storing the results. Useful for running the benchmarks in a profiler.")
778                 .conflicts_with_all(&["test", "list"]))
779             .arg(Arg::with_name("load-baseline")
780                  .long("load-baseline")
781                  .takes_value(true)
782                  .conflicts_with("profile-time")
783                  .requires("baseline")
784                  .help("Load a previous baseline instead of sampling new data."))
785             .arg(Arg::with_name("sample-size")
786                 .long("sample-size")
787                 .takes_value(true)
788                 .help(&format!("Changes the default size of the sample for this run. [default: {}]", self.config.sample_size)))
789             .arg(Arg::with_name("warm-up-time")
790                 .long("warm-up-time")
791                 .takes_value(true)
792                 .help(&format!("Changes the default warm up time for this run. [default: {}]", self.config.warm_up_time.as_secs())))
793             .arg(Arg::with_name("measurement-time")
794                 .long("measurement-time")
795                 .takes_value(true)
796                 .help(&format!("Changes the default measurement time for this run. [default: {}]", self.config.measurement_time.as_secs())))
797             .arg(Arg::with_name("nresamples")
798                 .long("nresamples")
799                 .takes_value(true)
800                 .help(&format!("Changes the default number of resamples for this run. [default: {}]", self.config.nresamples)))
801             .arg(Arg::with_name("noise-threshold")
802                 .long("noise-threshold")
803                 .takes_value(true)
804                 .help(&format!("Changes the default noise threshold for this run. [default: {}]", self.config.noise_threshold)))
805             .arg(Arg::with_name("confidence-level")
806                 .long("confidence-level")
807                 .takes_value(true)
808                 .help(&format!("Changes the default confidence level for this run. [default: {}]", self.config.confidence_level)))
809             .arg(Arg::with_name("significance-level")
810                 .long("significance-level")
811                 .takes_value(true)
812                 .help(&format!("Changes the default significance level for this run. [default: {}]", self.config.significance_level)))
813             .arg(Arg::with_name("test")
814                 .hidden(true)
815                 .long("test")
816                 .help("Run the benchmarks once, to verify that they execute successfully, but do not measure or report the results.")
817                 .conflicts_with_all(&["list", "profile-time"]))
818             .arg(Arg::with_name("bench")
819                 .hidden(true)
820                 .long("bench"))
821             .arg(Arg::with_name("plotting-backend")
822                  .long("plotting-backend")
823                  .takes_value(true)
824                  .possible_values(&["gnuplot", "plotters"])
825                  .help("Set the plotting backend. By default, Criterion.rs will use the gnuplot backend if gnuplot is available, or the plotters backend if it isn't."))
826             .arg(Arg::with_name("output-format")
827                 .long("output-format")
828                 .takes_value(true)
829                 .possible_values(&["criterion", "bencher"])
830                 .default_value("criterion")
831                 .help("Change the CLI output format. By default, Criterion.rs will use its own format. If output format is set to 'bencher', Criterion.rs will print output in a format that resembles the 'bencher' crate."))
832             .arg(Arg::with_name("nocapture")
833                 .long("nocapture")
834                 .hidden(true)
835                 .help("Ignored, but added for compatibility with libtest."))
836             .arg(Arg::with_name("version")
837                 .hidden(true)
838                 .short("V")
839                 .long("version"))
840             .after_help("
841 This executable is a Criterion.rs benchmark.
842 See https://github.com/bheisler/criterion.rs for more details.
843 
844 To enable debug output, define the environment variable CRITERION_DEBUG.
845 Criterion.rs will output more debug information and will save the gnuplot
846 scripts alongside the generated plots.
847 
848 To test that the benchmarks work, run `cargo test --benches`
849 
850 NOTE: If you see an 'unrecognized option' error using any of the options above, see:
851 https://bheisler.github.io/criterion.rs/book/faq.html
852 ")
853             .get_matches();
854 
855         if self.connection.is_some() {
856             if let Some(color) = matches.value_of("color") {
857                 if color != "auto" {
858                     println!("Warning: --color will be ignored when running with cargo-criterion. Use `cargo criterion --color {} -- <args>` instead.", color);
859                 }
860             }
861             if matches.is_present("verbose") {
862                 println!("Warning: --verbose will be ignored when running with cargo-criterion. Use `cargo criterion --output-format verbose -- <args>` instead.");
863             }
864             if matches.is_present("noplot") {
865                 println!("Warning: --noplot will be ignored when running with cargo-criterion. Use `cargo criterion --plotting-backend disabled -- <args>` instead.");
866             }
867             if let Some(backend) = matches.value_of("plotting-backend") {
868                 println!("Warning: --plotting-backend will be ignored when running with cargo-criterion. Use `cargo criterion --plotting-backend {} -- <args>` instead.", backend);
869             }
870             if let Some(format) = matches.value_of("output-format") {
871                 if format != "criterion" {
872                     println!("Warning: --output-format will be ignored when running with cargo-criterion. Use `cargo criterion --output-format {} -- <args>` instead.", format);
873                 }
874             }
875 
876             if matches.is_present("baseline")
877                 || matches
878                     .value_of("save-baseline")
879                     .map(|base| base != "base")
880                     .unwrap_or(false)
881                 || matches.is_present("load-baseline")
882             {
883                 println!("Error: baselines are not supported when running with cargo-criterion.");
884                 std::process::exit(1);
885             }
886         }
887 
888         let bench = matches.is_present("bench");
889         let test = matches.is_present("test");
890         let test_mode = match (bench, test) {
891             (true, true) => true,   // cargo bench -- --test should run tests
892             (true, false) => false, // cargo bench should run benchmarks
893             (false, _) => true,     // cargo test --benches should run tests
894         };
895 
896         self.mode = if test_mode {
897             Mode::Test
898         } else if matches.is_present("list") {
899             Mode::List
900         } else if matches.is_present("profile-time") {
901             let num_seconds = value_t!(matches.value_of("profile-time"), u64).unwrap_or_else(|e| {
902                 println!("{}", e);
903                 std::process::exit(1)
904             });
905 
906             if num_seconds < 1 {
907                 println!("Profile time must be at least one second.");
908                 std::process::exit(1);
909             }
910 
911             Mode::Profile(Duration::from_secs(num_seconds))
912         } else {
913             Mode::Benchmark
914         };
915 
916         // This is kind of a hack, but disable the connection to the runner if we're not benchmarking.
917         if !self.mode.is_benchmark() {
918             self.connection = None;
919         }
920 
921         if let Some(filter) = matches.value_of("FILTER") {
922             self = self.with_filter(filter);
923         }
924 
925         match matches.value_of("plotting-backend") {
926             // Use plotting_backend() here to re-use the panic behavior if Gnuplot is not available.
927             Some("gnuplot") => self = self.plotting_backend(PlottingBackend::Gnuplot),
928             Some("plotters") => self = self.plotting_backend(PlottingBackend::Plotters),
929             Some(val) => panic!("Unexpected plotting backend '{}'", val),
930             None => {}
931         }
932 
933         if matches.is_present("noplot") {
934             self = self.without_plots();
935         } else {
936             self = self.with_plots();
937         }
938 
939         if let Some(dir) = matches.value_of("save-baseline") {
940             self.baseline = Baseline::Save;
941             self.baseline_directory = dir.to_owned()
942         }
943         if let Some(dir) = matches.value_of("baseline") {
944             self.baseline = Baseline::Compare;
945             self.baseline_directory = dir.to_owned();
946         }
947 
948         if self.connection.is_some() {
949             // disable all reports when connected to cargo-criterion; it will do the reporting.
950             self.report.cli_enabled = false;
951             self.report.bencher_enabled = false;
952             self.report.csv_enabled = false;
953             self.report.html_enabled = false;
954         } else {
955             match matches.value_of("output-format") {
956                 Some("bencher") => {
957                     self.report.bencher_enabled = true;
958                     self.report.cli_enabled = false;
959                 }
960                 _ => {
961                     let verbose = matches.is_present("verbose");
962                     let stdout_isatty = atty::is(atty::Stream::Stdout);
963                     let mut enable_text_overwrite = stdout_isatty && !verbose && !debug_enabled();
964                     let enable_text_coloring;
965                     match matches.value_of("color") {
966                         Some("always") => {
967                             enable_text_coloring = true;
968                         }
969                         Some("never") => {
970                             enable_text_coloring = false;
971                             enable_text_overwrite = false;
972                         }
973                         _ => enable_text_coloring = stdout_isatty,
974                     };
975                     self.report.bencher_enabled = false;
976                     self.report.cli_enabled = true;
977                     self.report.cli =
978                         CliReport::new(enable_text_overwrite, enable_text_coloring, verbose);
979                 }
980             };
981         }
982 
983         if let Some(dir) = matches.value_of("load-baseline") {
984             self.load_baseline = Some(dir.to_owned());
985         }
986 
987         if matches.is_present("sample-size") {
988             let num_size = value_t!(matches.value_of("sample-size"), usize).unwrap_or_else(|e| {
989                 println!("{}", e);
990                 std::process::exit(1)
991             });
992 
993             assert!(num_size >= 10);
994             self.config.sample_size = num_size;
995         }
996         if matches.is_present("warm-up-time") {
997             let num_seconds = value_t!(matches.value_of("warm-up-time"), u64).unwrap_or_else(|e| {
998                 println!("{}", e);
999                 std::process::exit(1)
1000             });
1001 
1002             let dur = std::time::Duration::new(num_seconds, 0);
1003             assert!(dur.to_nanos() > 0);
1004 
1005             self.config.warm_up_time = dur;
1006         }
1007         if matches.is_present("measurement-time") {
1008             let num_seconds =
1009                 value_t!(matches.value_of("measurement-time"), u64).unwrap_or_else(|e| {
1010                     println!("{}", e);
1011                     std::process::exit(1)
1012                 });
1013 
1014             let dur = std::time::Duration::new(num_seconds, 0);
1015             assert!(dur.to_nanos() > 0);
1016 
1017             self.config.measurement_time = dur;
1018         }
1019         if matches.is_present("nresamples") {
1020             let num_resamples =
1021                 value_t!(matches.value_of("nresamples"), usize).unwrap_or_else(|e| {
1022                     println!("{}", e);
1023                     std::process::exit(1)
1024                 });
1025 
1026             assert!(num_resamples > 0);
1027 
1028             self.config.nresamples = num_resamples;
1029         }
1030         if matches.is_present("noise-threshold") {
1031             let num_noise_threshold = value_t!(matches.value_of("noise-threshold"), f64)
1032                 .unwrap_or_else(|e| {
1033                     println!("{}", e);
1034                     std::process::exit(1)
1035                 });
1036 
1037             assert!(num_noise_threshold > 0.0);
1038 
1039             self.config.noise_threshold = num_noise_threshold;
1040         }
1041         if matches.is_present("confidence-level") {
1042             let num_confidence_level = value_t!(matches.value_of("confidence-level"), f64)
1043                 .unwrap_or_else(|e| {
1044                     println!("{}", e);
1045                     std::process::exit(1)
1046                 });
1047 
1048             assert!(num_confidence_level > 0.0 && num_confidence_level < 1.0);
1049 
1050             self.config.confidence_level = num_confidence_level;
1051         }
1052         if matches.is_present("significance-level") {
1053             let num_significance_level = value_t!(matches.value_of("significance-level"), f64)
1054                 .unwrap_or_else(|e| {
1055                     println!("{}", e);
1056                     std::process::exit(1)
1057                 });
1058 
1059             assert!(num_significance_level > 0.0 && num_significance_level < 1.0);
1060 
1061             self.config.significance_level = num_significance_level;
1062         }
1063 
1064         self
1065     }
1066 
filter_matches(&self, id: &str) -> bool1067     fn filter_matches(&self, id: &str) -> bool {
1068         match self.filter {
1069             Some(ref regex) => regex.is_match(id),
1070             None => true,
1071         }
1072     }
1073 
1074     /// Return a benchmark group. All benchmarks performed using a benchmark group will be
1075     /// grouped together in the final report.
1076     ///
1077     /// # Examples:
1078     ///
1079     /// ```rust
1080     /// #[macro_use] extern crate criterion;
1081     /// use self::criterion::*;
1082     ///
1083     /// fn bench_simple(c: &mut Criterion) {
1084     ///     let mut group = c.benchmark_group("My Group");
1085     ///
1086     ///     // Now we can perform benchmarks with this group
1087     ///     group.bench_function("Bench 1", |b| b.iter(|| 1 ));
1088     ///     group.bench_function("Bench 2", |b| b.iter(|| 2 ));
1089     ///
1090     ///     group.finish();
1091     /// }
1092     /// criterion_group!(benches, bench_simple);
1093     /// criterion_main!(benches);
1094     /// ```
1095     /// # Panics:
1096     /// Panics if the group name is empty
benchmark_group<S: Into<String>>(&mut self, group_name: S) -> BenchmarkGroup<'_, M>1097     pub fn benchmark_group<S: Into<String>>(&mut self, group_name: S) -> BenchmarkGroup<'_, M> {
1098         let group_name = group_name.into();
1099         if group_name.is_empty() {
1100             panic!("Group name must not be empty.");
1101         }
1102 
1103         if let Some(conn) = &self.connection {
1104             conn.send(&OutgoingMessage::BeginningBenchmarkGroup { group: &group_name })
1105                 .unwrap();
1106         }
1107 
1108         BenchmarkGroup::new(self, group_name)
1109     }
1110 }
1111 impl<M> Criterion<M>
1112 where
1113     M: Measurement + 'static,
1114 {
1115     /// Benchmarks a function. For comparing multiple functions, see `benchmark_group`.
1116     ///
1117     /// # Example
1118     ///
1119     /// ```rust
1120     /// #[macro_use] extern crate criterion;
1121     /// use self::criterion::*;
1122     ///
1123     /// fn bench(c: &mut Criterion) {
1124     ///     // Setup (construct data, allocate memory, etc)
1125     ///     c.bench_function(
1126     ///         "function_name",
1127     ///         |b| b.iter(|| {
1128     ///             // Code to benchmark goes here
1129     ///         }),
1130     ///     );
1131     /// }
1132     ///
1133     /// criterion_group!(benches, bench);
1134     /// criterion_main!(benches);
1135     /// ```
bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion<M> where F: FnMut(&mut Bencher<'_, M>),1136     pub fn bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion<M>
1137     where
1138         F: FnMut(&mut Bencher<'_, M>),
1139     {
1140         self.benchmark_group(id)
1141             .bench_function(BenchmarkId::no_function(), f);
1142         self
1143     }
1144 
1145     /// Benchmarks a function with an input. For comparing multiple functions or multiple inputs,
1146     /// see `benchmark_group`.
1147     ///
1148     /// # Example
1149     ///
1150     /// ```rust
1151     /// #[macro_use] extern crate criterion;
1152     /// use self::criterion::*;
1153     ///
1154     /// fn bench(c: &mut Criterion) {
1155     ///     // Setup (construct data, allocate memory, etc)
1156     ///     let input = 5u64;
1157     ///     c.bench_with_input(
1158     ///         BenchmarkId::new("function_name", input), &input,
1159     ///         |b, i| b.iter(|| {
1160     ///             // Code to benchmark using input `i` goes here
1161     ///         }),
1162     ///     );
1163     /// }
1164     ///
1165     /// criterion_group!(benches, bench);
1166     /// criterion_main!(benches);
1167     /// ```
bench_with_input<F, I>(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion<M> where F: FnMut(&mut Bencher<'_, M>, &I),1168     pub fn bench_with_input<F, I>(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion<M>
1169     where
1170         F: FnMut(&mut Bencher<'_, M>, &I),
1171     {
1172         // It's possible to use BenchmarkId::from_parameter to create a benchmark ID with no function
1173         // name. That's intended for use with BenchmarkGroups where the function name isn't necessary,
1174         // but here it is.
1175         let group_name = id.function_name.expect(
1176             "Cannot use BenchmarkId::from_parameter with Criterion::bench_with_input. \
1177                  Consider using a BenchmarkGroup or BenchmarkId::new instead.",
1178         );
1179         // Guaranteed safe because external callers can't create benchmark IDs without a parameter
1180         let parameter = id.parameter.unwrap();
1181         self.benchmark_group(group_name).bench_with_input(
1182             BenchmarkId::no_function_with_input(parameter),
1183             input,
1184             f,
1185         );
1186         self
1187     }
1188 
1189     /// Benchmarks a function under various inputs
1190     ///
1191     /// This is a convenience method to execute several related benchmarks. Each benchmark will
1192     /// receive the id: `${id}/${input}`.
1193     ///
1194     /// # Example
1195     ///
1196     /// ```rust
1197     /// # #[macro_use] extern crate criterion;
1198     /// # use self::criterion::*;
1199     ///
1200     /// fn bench(c: &mut Criterion) {
1201     ///     c.bench_function_over_inputs("from_elem",
1202     ///         |b: &mut Bencher, size: &usize| {
1203     ///             b.iter(|| vec![0u8; *size]);
1204     ///         },
1205     ///         vec![1024, 2048, 4096]
1206     ///     );
1207     /// }
1208     ///
1209     /// criterion_group!(benches, bench);
1210     /// criterion_main!(benches);
1211     /// ```
1212     #[doc(hidden)]
1213     #[deprecated(since = "0.3.4", note = "Please use BenchmarkGroups instead.")]
1214     #[allow(deprecated)]
bench_function_over_inputs<I, F>( &mut self, id: &str, f: F, inputs: I, ) -> &mut Criterion<M> where I: IntoIterator, I::Item: fmt::Debug + 'static, F: FnMut(&mut Bencher<'_, M>, &I::Item) + 'static,1215     pub fn bench_function_over_inputs<I, F>(
1216         &mut self,
1217         id: &str,
1218         f: F,
1219         inputs: I,
1220     ) -> &mut Criterion<M>
1221     where
1222         I: IntoIterator,
1223         I::Item: fmt::Debug + 'static,
1224         F: FnMut(&mut Bencher<'_, M>, &I::Item) + 'static,
1225     {
1226         self.bench(id, ParameterizedBenchmark::new(id, f, inputs))
1227     }
1228 
1229     /// Benchmarks multiple functions
1230     ///
1231     /// All functions get the same input and are compared with the other implementations.
1232     /// Works similar to `bench_function`, but with multiple functions.
1233     ///
1234     /// # Example
1235     ///
1236     /// ``` rust
1237     /// # #[macro_use] extern crate criterion;
1238     /// # use self::criterion::*;
1239     /// # fn seq_fib(i: &u32) {}
1240     /// # fn par_fib(i: &u32) {}
1241     ///
1242     /// fn bench_seq_fib(b: &mut Bencher, i: &u32) {
1243     ///     b.iter(|| {
1244     ///         seq_fib(i);
1245     ///     });
1246     /// }
1247     ///
1248     /// fn bench_par_fib(b: &mut Bencher, i: &u32) {
1249     ///     b.iter(|| {
1250     ///         par_fib(i);
1251     ///     });
1252     /// }
1253     ///
1254     /// fn bench(c: &mut Criterion) {
1255     ///     let sequential_fib = Fun::new("Sequential", bench_seq_fib);
1256     ///     let parallel_fib = Fun::new("Parallel", bench_par_fib);
1257     ///     let funs = vec![sequential_fib, parallel_fib];
1258     ///
1259     ///     c.bench_functions("Fibonacci", funs, 14);
1260     /// }
1261     ///
1262     /// criterion_group!(benches, bench);
1263     /// criterion_main!(benches);
1264     /// ```
1265     #[doc(hidden)]
1266     #[deprecated(since = "0.3.4", note = "Please use BenchmarkGroups instead.")]
1267     #[allow(deprecated)]
bench_functions<I>( &mut self, id: &str, funs: Vec<Fun<I, M>>, input: I, ) -> &mut Criterion<M> where I: fmt::Debug + 'static,1268     pub fn bench_functions<I>(
1269         &mut self,
1270         id: &str,
1271         funs: Vec<Fun<I, M>>,
1272         input: I,
1273     ) -> &mut Criterion<M>
1274     where
1275         I: fmt::Debug + 'static,
1276     {
1277         let benchmark = ParameterizedBenchmark::with_functions(
1278             funs.into_iter().map(|fun| fun.f).collect(),
1279             vec![input],
1280         );
1281 
1282         self.bench(id, benchmark)
1283     }
1284 
1285     /// Executes the given benchmark. Use this variant to execute benchmarks
1286     /// with complex configuration. This can be used to compare multiple
1287     /// functions, execute benchmarks with custom configuration settings and
1288     /// more. See the Benchmark and ParameterizedBenchmark structs for more
1289     /// information.
1290     ///
1291     /// ```rust
1292     /// # #[macro_use] extern crate criterion;
1293     /// # use criterion::*;
1294     /// # fn routine_1() {}
1295     /// # fn routine_2() {}
1296     ///
1297     /// fn bench(c: &mut Criterion) {
1298     ///     // Setup (construct data, allocate memory, etc)
1299     ///     c.bench(
1300     ///         "routines",
1301     ///         Benchmark::new("routine_1", |b| b.iter(|| routine_1()))
1302     ///             .with_function("routine_2", |b| b.iter(|| routine_2()))
1303     ///             .sample_size(50)
1304     ///     );
1305     /// }
1306     ///
1307     /// criterion_group!(benches, bench);
1308     /// criterion_main!(benches);
1309     /// ```
1310     #[doc(hidden)]
1311     #[deprecated(since = "0.3.4", note = "Please use BenchmarkGroups instead.")]
bench<B: BenchmarkDefinition<M>>( &mut self, group_id: &str, benchmark: B, ) -> &mut Criterion<M>1312     pub fn bench<B: BenchmarkDefinition<M>>(
1313         &mut self,
1314         group_id: &str,
1315         benchmark: B,
1316     ) -> &mut Criterion<M> {
1317         benchmark.run(group_id, self);
1318         self
1319     }
1320 }
1321 
1322 trait DurationExt {
to_nanos(&self) -> u641323     fn to_nanos(&self) -> u64;
1324 }
1325 
1326 const NANOS_PER_SEC: u64 = 1_000_000_000;
1327 
1328 impl DurationExt for Duration {
to_nanos(&self) -> u641329     fn to_nanos(&self) -> u64 {
1330         self.as_secs() * NANOS_PER_SEC + u64::from(self.subsec_nanos())
1331     }
1332 }
1333 
1334 /// Enum representing different ways of measuring the throughput of benchmarked code.
1335 /// If the throughput setting is configured for a benchmark then the estimated throughput will
1336 /// be reported as well as the time per iteration.
1337 // TODO: Remove serialize/deserialize from the public API.
1338 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1339 pub enum Throughput {
1340     /// Measure throughput in terms of bytes/second. The value should be the number of bytes
1341     /// processed by one iteration of the benchmarked code. Typically, this would be the length of
1342     /// an input string or `&[u8]`.
1343     Bytes(u64),
1344 
1345     /// Measure throughput in terms of elements/second. The value should be the number of elements
1346     /// processed by one iteration of the benchmarked code. Typically, this would be the size of a
1347     /// collection, but could also be the number of lines of input text or the number of values to
1348     /// parse.
1349     Elements(u64),
1350 }
1351 
1352 /// Axis scaling type
1353 #[derive(Debug, Clone, Copy)]
1354 pub enum AxisScale {
1355     /// Axes scale linearly
1356     Linear,
1357 
1358     /// Axes scale logarithmically
1359     Logarithmic,
1360 }
1361 
1362 /// Contains the configuration options for the plots generated by a particular benchmark
1363 /// or benchmark group.
1364 ///
1365 /// ```rust
1366 /// use self::criterion::{Bencher, Criterion, Benchmark, PlotConfiguration, AxisScale};
1367 ///
1368 /// let plot_config = PlotConfiguration::default()
1369 ///     .summary_scale(AxisScale::Logarithmic);
1370 ///
1371 /// // Using Criterion::default() for simplicity; normally you'd use the macros.
1372 /// let mut criterion = Criterion::default();
1373 /// let mut benchmark_group = criterion.benchmark_group("Group name");
1374 /// benchmark_group.plot_config(plot_config);
1375 /// // Use benchmark group
1376 /// ```
1377 #[derive(Debug, Clone)]
1378 pub struct PlotConfiguration {
1379     summary_scale: AxisScale,
1380 }
1381 
1382 impl Default for PlotConfiguration {
default() -> PlotConfiguration1383     fn default() -> PlotConfiguration {
1384         PlotConfiguration {
1385             summary_scale: AxisScale::Linear,
1386         }
1387     }
1388 }
1389 
1390 impl PlotConfiguration {
1391     /// Set the axis scale (linear or logarithmic) for the summary plots. Typically, you would
1392     /// set this to logarithmic if benchmarking over a range of inputs which scale exponentially.
1393     /// Defaults to linear.
summary_scale(mut self, new_scale: AxisScale) -> PlotConfiguration1394     pub fn summary_scale(mut self, new_scale: AxisScale) -> PlotConfiguration {
1395         self.summary_scale = new_scale;
1396         self
1397     }
1398 }
1399 
1400 /// This enum allows the user to control how Criterion.rs chooses the iteration count when sampling.
1401 /// The default is Auto, which will choose a method automatically based on the iteration time during
1402 /// the warm-up phase.
1403 #[derive(Debug, Clone, Copy)]
1404 pub enum SamplingMode {
1405     /// Criterion.rs should choose a sampling method automatically. This is the default, and is
1406     /// recommended for most users and most benchmarks.
1407     Auto,
1408 
1409     /// Scale the iteration count in each sample linearly. This is suitable for most benchmarks,
1410     /// but it tends to require many iterations which can make it very slow for very long benchmarks.
1411     Linear,
1412 
1413     /// Keep the iteration count the same for all samples. This is not recommended, as it affects
1414     /// the statistics that Criterion.rs can compute. However, it requires fewer iterations than
1415     /// the Linear method and therefore is more suitable for very long-running benchmarks where
1416     /// benchmark execution time is more of a problem and statistical precision is less important.
1417     Flat,
1418 }
1419 impl SamplingMode {
choose_sampling_mode( &self, warmup_mean_execution_time: f64, sample_count: u64, target_time: f64, ) -> ActualSamplingMode1420     pub(crate) fn choose_sampling_mode(
1421         &self,
1422         warmup_mean_execution_time: f64,
1423         sample_count: u64,
1424         target_time: f64,
1425     ) -> ActualSamplingMode {
1426         match self {
1427             SamplingMode::Linear => ActualSamplingMode::Linear,
1428             SamplingMode::Flat => ActualSamplingMode::Flat,
1429             SamplingMode::Auto => {
1430                 // Estimate execution time with linear sampling
1431                 let total_runs = sample_count * (sample_count + 1) / 2;
1432                 let d =
1433                     (target_time / warmup_mean_execution_time / total_runs as f64).ceil() as u64;
1434                 let expected_ns = total_runs as f64 * d as f64 * warmup_mean_execution_time;
1435 
1436                 if expected_ns > (2.0 * target_time) {
1437                     ActualSamplingMode::Flat
1438                 } else {
1439                     ActualSamplingMode::Linear
1440                 }
1441             }
1442         }
1443     }
1444 }
1445 
1446 /// Enum to represent the sampling mode without Auto.
1447 #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1448 pub(crate) enum ActualSamplingMode {
1449     Linear,
1450     Flat,
1451 }
1452 impl ActualSamplingMode {
iteration_counts( &self, warmup_mean_execution_time: f64, sample_count: u64, target_time: &Duration, ) -> Vec<u64>1453     pub(crate) fn iteration_counts(
1454         &self,
1455         warmup_mean_execution_time: f64,
1456         sample_count: u64,
1457         target_time: &Duration,
1458     ) -> Vec<u64> {
1459         match self {
1460             ActualSamplingMode::Linear => {
1461                 let n = sample_count;
1462                 let met = warmup_mean_execution_time;
1463                 let m_ns = target_time.to_nanos();
1464                 // Solve: [d + 2*d + 3*d + ... + n*d] * met = m_ns
1465                 let total_runs = n * (n + 1) / 2;
1466                 let d = ((m_ns as f64 / met / total_runs as f64).ceil() as u64).max(1);
1467                 let expected_ns = total_runs as f64 * d as f64 * met;
1468 
1469                 if d == 1 {
1470                     let recommended_sample_size =
1471                         ActualSamplingMode::recommend_linear_sample_size(m_ns as f64, met);
1472                     let actual_time = Duration::from_nanos(expected_ns as u64);
1473                     print!("\nWarning: Unable to complete {} samples in {:.1?}. You may wish to increase target time to {:.1?}",
1474                             n, target_time, actual_time);
1475 
1476                     if recommended_sample_size != n {
1477                         println!(
1478                             ", enable flat sampling, or reduce sample count to {}.",
1479                             recommended_sample_size
1480                         );
1481                     } else {
1482                         println!(" or enable flat sampling.");
1483                     }
1484                 }
1485 
1486                 (1..(n + 1) as u64).map(|a| a * d).collect::<Vec<u64>>()
1487             }
1488             ActualSamplingMode::Flat => {
1489                 let n = sample_count;
1490                 let met = warmup_mean_execution_time;
1491                 let m_ns = target_time.to_nanos() as f64;
1492                 let time_per_sample = m_ns / (n as f64);
1493                 // This is pretty simplistic; we could do something smarter to fit into the allotted time.
1494                 let iterations_per_sample = ((time_per_sample / met).ceil() as u64).max(1);
1495 
1496                 let expected_ns = met * (iterations_per_sample * n) as f64;
1497 
1498                 if iterations_per_sample == 1 {
1499                     let recommended_sample_size =
1500                         ActualSamplingMode::recommend_flat_sample_size(m_ns, met);
1501                     let actual_time = Duration::from_nanos(expected_ns as u64);
1502                     print!("\nWarning: Unable to complete {} samples in {:.1?}. You may wish to increase target time to {:.1?}",
1503                             n, target_time, actual_time);
1504 
1505                     if recommended_sample_size != n {
1506                         println!(", or reduce sample count to {}.", recommended_sample_size);
1507                     } else {
1508                         println!(".");
1509                     }
1510                 }
1511 
1512                 vec![iterations_per_sample; n as usize]
1513             }
1514         }
1515     }
1516 
is_linear(&self) -> bool1517     fn is_linear(&self) -> bool {
1518         match self {
1519             ActualSamplingMode::Linear => true,
1520             _ => false,
1521         }
1522     }
1523 
recommend_linear_sample_size(target_time: f64, met: f64) -> u641524     fn recommend_linear_sample_size(target_time: f64, met: f64) -> u64 {
1525         // Some math shows that n(n+1)/2 * d * met = target_time. d = 1, so it can be ignored.
1526         // This leaves n(n+1) = (2*target_time)/met, or n^2 + n - (2*target_time)/met = 0
1527         // Which can be solved with the quadratic formula. Since A and B are constant 1,
1528         // this simplifies to sample_size = (-1 +- sqrt(1 - 4C))/2, where C = (2*target_time)/met.
1529         // We don't care about the negative solution. Experimentation shows that this actually tends to
1530         // result in twice the desired execution time (probably because of the ceil used to calculate
1531         // d) so instead I use c = target_time/met.
1532         let c = target_time / met;
1533         let sample_size = (-1.0 + (4.0 * c).sqrt()) / 2.0;
1534         let sample_size = sample_size as u64;
1535 
1536         // Round down to the nearest 10 to give a margin and avoid excessive precision
1537         let sample_size = (sample_size / 10) * 10;
1538 
1539         // Clamp it to be at least 10, since criterion.rs doesn't allow sample sizes smaller than 10.
1540         if sample_size < 10 {
1541             10
1542         } else {
1543             sample_size
1544         }
1545     }
1546 
recommend_flat_sample_size(target_time: f64, met: f64) -> u641547     fn recommend_flat_sample_size(target_time: f64, met: f64) -> u64 {
1548         let sample_size = (target_time / met) as u64;
1549 
1550         // Round down to the nearest 10 to give a margin and avoid excessive precision
1551         let sample_size = (sample_size / 10) * 10;
1552 
1553         // Clamp it to be at least 10, since criterion.rs doesn't allow sample sizes smaller than 10.
1554         if sample_size < 10 {
1555             10
1556         } else {
1557             sample_size
1558         }
1559     }
1560 }
1561 
1562 #[derive(Debug, Serialize, Deserialize)]
1563 pub(crate) struct SavedSample {
1564     sampling_mode: ActualSamplingMode,
1565     iters: Vec<f64>,
1566     times: Vec<f64>,
1567 }
1568 
1569 /// Custom-test-framework runner. Should not be called directly.
1570 #[doc(hidden)]
runner(benches: &[&dyn Fn()])1571 pub fn runner(benches: &[&dyn Fn()]) {
1572     for bench in benches {
1573         bench();
1574     }
1575     Criterion::default().configure_from_args().final_summary();
1576 }
1577 
1578 /// Print a warning informing users about upcoming changes to features
1579 #[cfg(not(feature = "html_reports"))]
1580 #[doc(hidden)]
__warn_about_html_reports_feature()1581 pub fn __warn_about_html_reports_feature() {
1582     if CARGO_CRITERION_CONNECTION.is_none() {
1583         println!(
1584             "WARNING: HTML report generation will become a non-default optional feature in Criterion.rs 0.4.0."
1585         );
1586         println!(
1587             "This feature is being moved to cargo-criterion \
1588             (https://github.com/bheisler/cargo-criterion) and will be optional in a future \
1589             version of Criterion.rs. To silence this warning, either switch to cargo-criterion or \
1590             enable the 'html_reports' feature in your Cargo.toml."
1591         );
1592         println!();
1593     }
1594 }
1595 
1596 /// Print a warning informing users about upcoming changes to features
1597 #[cfg(feature = "html_reports")]
1598 #[doc(hidden)]
__warn_about_html_reports_feature()1599 pub fn __warn_about_html_reports_feature() {
1600     // They have the feature enabled, so they're ready for the update.
1601 }
1602 
1603 /// Print a warning informing users about upcoming changes to features
1604 #[cfg(not(feature = "cargo_bench_support"))]
1605 #[doc(hidden)]
__warn_about_cargo_bench_support_feature()1606 pub fn __warn_about_cargo_bench_support_feature() {
1607     if CARGO_CRITERION_CONNECTION.is_none() {
1608         println!(
1609             "WARNING: In Criterion.rs 0.4.0, running criterion benchmarks outside of cargo-criterion will become a default optional feature."
1610         );
1611         println!(
1612             "The statistical analysis and reporting is being moved to cargo-criterion \
1613             (https://github.com/bheisler/cargo-criterion) and will be optional in a future \
1614             version of Criterion.rs. To silence this warning, either switch to cargo-criterion or \
1615             enable the 'cargo_bench_support' feature in your Cargo.toml."
1616         );
1617         println!();
1618     }
1619 }
1620 
1621 /// Print a warning informing users about upcoming changes to features
1622 #[cfg(feature = "cargo_bench_support")]
1623 #[doc(hidden)]
__warn_about_cargo_bench_support_feature()1624 pub fn __warn_about_cargo_bench_support_feature() {
1625     // They have the feature enabled, so they're ready for the update.
1626 }
1627