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