• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::analysis;
2 use crate::benchmark::PartialBenchmarkConfig;
3 use crate::connection::OutgoingMessage;
4 use crate::measurement::Measurement;
5 use crate::report::BenchmarkId as InternalBenchmarkId;
6 use crate::report::Report;
7 use crate::report::ReportContext;
8 use crate::routine::{Function, Routine};
9 use crate::{Bencher, Criterion, DurationExt, Mode, PlotConfiguration, SamplingMode, Throughput};
10 use std::time::Duration;
11 
12 /// Structure used to group together a set of related benchmarks, along with custom configuration
13 /// settings for groups of benchmarks. All benchmarks performed using a benchmark group will be
14 /// grouped together in the final report.
15 ///
16 /// # Examples:
17 ///
18 /// ```no_run
19 /// #[macro_use] extern crate criterion;
20 /// use self::criterion::*;
21 /// use std::time::Duration;
22 ///
23 /// fn bench_simple(c: &mut Criterion) {
24 ///     let mut group = c.benchmark_group("My Group");
25 ///
26 ///     // Now we can perform benchmarks with this group
27 ///     group.bench_function("Bench 1", |b| b.iter(|| 1 ));
28 ///     group.bench_function("Bench 2", |b| b.iter(|| 2 ));
29 ///
30 ///     // It's recommended to call group.finish() explicitly at the end, but if you don't it will
31 ///     // be called automatically when the group is dropped.
32 ///     group.finish();
33 /// }
34 ///
35 /// fn bench_nested(c: &mut Criterion) {
36 ///     let mut group = c.benchmark_group("My Second Group");
37 ///     // We can override the configuration on a per-group level
38 ///     group.measurement_time(Duration::from_secs(1));
39 ///
40 ///     // We can also use loops to define multiple benchmarks, even over multiple dimensions.
41 ///     for x in 0..3 {
42 ///         for y in 0..3 {
43 ///             let point = (x, y);
44 ///             let parameter_string = format!("{} * {}", x, y);
45 ///             group.bench_with_input(BenchmarkId::new("Multiply", parameter_string), &point,
46 ///                 |b, (p_x, p_y)| b.iter(|| p_x * p_y));
47 ///         }
48 ///     }
49 ///
50 ///     group.finish();
51 /// }
52 ///
53 /// fn bench_throughput(c: &mut Criterion) {
54 ///     let mut group = c.benchmark_group("Summation");
55 ///
56 ///     for size in [1024, 2048, 4096].iter() {
57 ///         // Generate input of an appropriate size...
58 ///         let input = vec![1u64, *size];
59 ///
60 ///         // We can use the throughput function to tell Criterion.rs how large the input is
61 ///         // so it can calculate the overall throughput of the function. If we wanted, we could
62 ///         // even change the benchmark configuration for different inputs (eg. to reduce the
63 ///         // number of samples for extremely large and slow inputs) or even different functions.
64 ///         group.throughput(Throughput::Elements(*size as u64));
65 ///
66 ///         group.bench_with_input(BenchmarkId::new("sum", *size), &input,
67 ///             |b, i| b.iter(|| i.iter().sum::<u64>()));
68 ///         group.bench_with_input(BenchmarkId::new("fold", *size), &input,
69 ///             |b, i| b.iter(|| i.iter().fold(0u64, |a, b| a + b)));
70 ///     }
71 ///
72 ///     group.finish();
73 /// }
74 ///
75 /// criterion_group!(benches, bench_simple, bench_nested, bench_throughput);
76 /// criterion_main!(benches);
77 /// ```
78 pub struct BenchmarkGroup<'a, M: Measurement> {
79     criterion: &'a mut Criterion<M>,
80     group_name: String,
81     all_ids: Vec<InternalBenchmarkId>,
82     any_matched: bool,
83     partial_config: PartialBenchmarkConfig,
84     throughput: Option<Throughput>,
85 }
86 impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
87     /// Changes the size of the sample for this benchmark
88     ///
89     /// A bigger sample should yield more accurate results if paired with a sufficiently large
90     /// measurement time.
91     ///
92     /// Sample size must be at least 10.
93     ///
94     /// # Panics
95     ///
96     /// Panics if n < 10.
sample_size(&mut self, n: usize) -> &mut Self97     pub fn sample_size(&mut self, n: usize) -> &mut Self {
98         assert!(n >= 10);
99 
100         self.partial_config.sample_size = Some(n);
101         self
102     }
103 
104     /// Changes the warm up time for this benchmark
105     ///
106     /// # Panics
107     ///
108     /// Panics if the input duration is zero
warm_up_time(&mut self, dur: Duration) -> &mut Self109     pub fn warm_up_time(&mut self, dur: Duration) -> &mut Self {
110         assert!(dur.to_nanos() > 0);
111 
112         self.partial_config.warm_up_time = Some(dur);
113         self
114     }
115 
116     /// Changes the target measurement time for this benchmark group.
117     ///
118     /// Criterion will attempt to spent approximately this amount of time measuring each
119     /// benchmark on a best-effort basis. If it is not possible to perform the measurement in
120     /// the requested time (eg. because each iteration of the benchmark is long) then Criterion
121     /// will spend as long as is needed to collect the desired number of samples. With a longer
122     /// time, the measurement will become more resilient to interference from other programs.
123     ///
124     /// # Panics
125     ///
126     /// Panics if the input duration is zero
measurement_time(&mut self, dur: Duration) -> &mut Self127     pub fn measurement_time(&mut self, dur: Duration) -> &mut Self {
128         assert!(dur.to_nanos() > 0);
129 
130         self.partial_config.measurement_time = Some(dur);
131         self
132     }
133 
134     /// Changes the number of resamples for this benchmark group
135     ///
136     /// Number of resamples to use for the
137     /// [bootstrap](http://en.wikipedia.org/wiki/Bootstrapping_(statistics)#Case_resampling)
138     ///
139     /// A larger number of resamples reduces the random sampling errors which are inherent to the
140     /// bootstrap method, but also increases the analysis time.
141     ///
142     /// # Panics
143     ///
144     /// Panics if the number of resamples is set to zero
nresamples(&mut self, n: usize) -> &mut Self145     pub fn nresamples(&mut self, n: usize) -> &mut Self {
146         assert!(n > 0);
147         if n <= 1000 {
148             println!("\nWarning: It is not recommended to reduce nresamples below 1000.");
149         }
150 
151         self.partial_config.nresamples = Some(n);
152         self
153     }
154 
155     /// Changes the noise threshold for benchmarks in this group. The noise threshold
156     /// is used to filter out small changes in performance from one run to the next, even if they
157     /// are statistically significant. Sometimes benchmarking the same code twice will result in
158     /// small but statistically significant differences solely because of noise. This provides a way
159     /// to filter out some of these false positives at the cost of making it harder to detect small
160     /// changes to the true performance of the benchmark.
161     ///
162     /// The default is 0.01, meaning that changes smaller than 1% will be ignored.
163     ///
164     /// # Panics
165     ///
166     /// Panics if the threshold is set to a negative value
noise_threshold(&mut self, threshold: f64) -> &mut Self167     pub fn noise_threshold(&mut self, threshold: f64) -> &mut Self {
168         assert!(threshold >= 0.0);
169 
170         self.partial_config.noise_threshold = Some(threshold);
171         self
172     }
173 
174     /// Changes the confidence level for benchmarks in this group. The confidence
175     /// level is the desired probability that the true runtime lies within the estimated
176     /// [confidence interval](https://en.wikipedia.org/wiki/Confidence_interval). The default is
177     /// 0.95, meaning that the confidence interval should capture the true value 95% of the time.
178     ///
179     /// # Panics
180     ///
181     /// Panics if the confidence level is set to a value outside the `(0, 1)` range
confidence_level(&mut self, cl: f64) -> &mut Self182     pub fn confidence_level(&mut self, cl: f64) -> &mut Self {
183         assert!(cl > 0.0 && cl < 1.0);
184         if cl < 0.5 {
185             println!("\nWarning: It is not recommended to reduce confidence level below 0.5.");
186         }
187 
188         self.partial_config.confidence_level = Some(cl);
189         self
190     }
191 
192     /// Changes the [significance level](https://en.wikipedia.org/wiki/Statistical_significance)
193     /// for benchmarks in this group. This is used to perform a
194     /// [hypothesis test](https://en.wikipedia.org/wiki/Statistical_hypothesis_testing) to see if
195     /// the measurements from this run are different from the measured performance of the last run.
196     /// The significance level is the desired probability that two measurements of identical code
197     /// will be considered 'different' due to noise in the measurements. The default value is 0.05,
198     /// meaning that approximately 5% of identical benchmarks will register as different due to
199     /// noise.
200     ///
201     /// This presents a trade-off. By setting the significance level closer to 0.0, you can increase
202     /// the statistical robustness against noise, but it also weakens Criterion.rs' ability to
203     /// detect small but real changes in the performance. By setting the significance level
204     /// closer to 1.0, Criterion.rs will be more able to detect small true changes, but will also
205     /// report more spurious differences.
206     ///
207     /// See also the noise threshold setting.
208     ///
209     /// # Panics
210     ///
211     /// Panics if the significance level is set to a value outside the `(0, 1)` range
significance_level(&mut self, sl: f64) -> &mut Self212     pub fn significance_level(&mut self, sl: f64) -> &mut Self {
213         assert!(sl > 0.0 && sl < 1.0);
214 
215         self.partial_config.significance_level = Some(sl);
216         self
217     }
218 
219     /// Changes the plot configuration for this benchmark group.
plot_config(&mut self, new_config: PlotConfiguration) -> &mut Self220     pub fn plot_config(&mut self, new_config: PlotConfiguration) -> &mut Self {
221         self.partial_config.plot_config = new_config;
222         self
223     }
224 
225     /// Set the input size for this benchmark group. Used for reporting the
226     /// throughput.
throughput(&mut self, throughput: Throughput) -> &mut Self227     pub fn throughput(&mut self, throughput: Throughput) -> &mut Self {
228         self.throughput = Some(throughput);
229         self
230     }
231 
232     /// Set the sampling mode for this benchmark group.
sampling_mode(&mut self, new_mode: SamplingMode) -> &mut Self233     pub fn sampling_mode(&mut self, new_mode: SamplingMode) -> &mut Self {
234         self.partial_config.sampling_mode = Some(new_mode);
235         self
236     }
237 
new(criterion: &mut Criterion<M>, group_name: String) -> BenchmarkGroup<'_, M>238     pub(crate) fn new(criterion: &mut Criterion<M>, group_name: String) -> BenchmarkGroup<'_, M> {
239         BenchmarkGroup {
240             criterion,
241             group_name,
242             all_ids: vec![],
243             any_matched: false,
244             partial_config: PartialBenchmarkConfig::default(),
245             throughput: None,
246         }
247     }
248 
249     /// Benchmark the given parameterless function inside this benchmark group.
bench_function<ID: IntoBenchmarkId, F>(&mut self, id: ID, mut f: F) -> &mut Self where F: FnMut(&mut Bencher<'_, M>),250     pub fn bench_function<ID: IntoBenchmarkId, F>(&mut self, id: ID, mut f: F) -> &mut Self
251     where
252         F: FnMut(&mut Bencher<'_, M>),
253     {
254         self.run_bench(id.into_benchmark_id(), &(), |b, _| f(b));
255         self
256     }
257 
258     /// Benchmark the given parameterized function inside this benchmark group.
bench_with_input<ID: IntoBenchmarkId, F, I>( &mut self, id: ID, input: &I, f: F, ) -> &mut Self where F: FnMut(&mut Bencher<'_, M>, &I), I: ?Sized,259     pub fn bench_with_input<ID: IntoBenchmarkId, F, I>(
260         &mut self,
261         id: ID,
262         input: &I,
263         f: F,
264     ) -> &mut Self
265     where
266         F: FnMut(&mut Bencher<'_, M>, &I),
267         I: ?Sized,
268     {
269         self.run_bench(id.into_benchmark_id(), input, f);
270         self
271     }
272 
run_bench<F, I>(&mut self, id: BenchmarkId, input: &I, f: F) where F: FnMut(&mut Bencher<'_, M>, &I), I: ?Sized,273     fn run_bench<F, I>(&mut self, id: BenchmarkId, input: &I, f: F)
274     where
275         F: FnMut(&mut Bencher<'_, M>, &I),
276         I: ?Sized,
277     {
278         let config = self.partial_config.to_complete(&self.criterion.config);
279         let report_context = ReportContext {
280             output_directory: self.criterion.output_directory.clone(),
281             plot_config: self.partial_config.plot_config.clone(),
282         };
283 
284         let mut id = InternalBenchmarkId::new(
285             self.group_name.clone(),
286             id.function_name,
287             id.parameter,
288             self.throughput.clone(),
289         );
290 
291         assert!(
292             !self.all_ids.contains(&id),
293             "Benchmark IDs must be unique within a group."
294         );
295 
296         id.ensure_directory_name_unique(&self.criterion.all_directories);
297         self.criterion
298             .all_directories
299             .insert(id.as_directory_name().to_owned());
300         id.ensure_title_unique(&self.criterion.all_titles);
301         self.criterion.all_titles.insert(id.as_title().to_owned());
302 
303         let do_run = self.criterion.filter_matches(id.id());
304         self.any_matched |= do_run;
305         let mut func = Function::new(f);
306 
307         match self.criterion.mode {
308             Mode::Benchmark => {
309                 if let Some(conn) = &self.criterion.connection {
310                     if do_run {
311                         conn.send(&OutgoingMessage::BeginningBenchmark { id: (&id).into() })
312                             .unwrap();
313                     } else {
314                         conn.send(&OutgoingMessage::SkippingBenchmark { id: (&id).into() })
315                             .unwrap();
316                     }
317                 }
318                 if do_run {
319                     analysis::common(
320                         &id,
321                         &mut func,
322                         &config,
323                         self.criterion,
324                         &report_context,
325                         input,
326                         self.throughput.clone(),
327                     );
328                 }
329             }
330             Mode::List => {
331                 if do_run {
332                     println!("{}: bench", id);
333                 }
334             }
335             Mode::Test => {
336                 if do_run {
337                     // In test mode, run the benchmark exactly once, then exit.
338                     self.criterion.report.test_start(&id, &report_context);
339                     func.test(&self.criterion.measurement, input);
340                     self.criterion.report.test_pass(&id, &report_context);
341                 }
342             }
343             Mode::Profile(duration) => {
344                 if do_run {
345                     func.profile(
346                         &self.criterion.measurement,
347                         &id,
348                         &self.criterion,
349                         &report_context,
350                         duration,
351                         input,
352                     );
353                 }
354             }
355         }
356 
357         self.all_ids.push(id);
358     }
359 
360     /// Consume the benchmark group and generate the summary reports for the group.
361     ///
362     /// It is recommended to call this explicitly, but if you forget it will be called when the
363     /// group is dropped.
finish(self)364     pub fn finish(self) {
365         ::std::mem::drop(self);
366     }
367 }
368 impl<'a, M: Measurement> Drop for BenchmarkGroup<'a, M> {
drop(&mut self)369     fn drop(&mut self) {
370         // I don't really like having a bunch of non-trivial code in drop, but this is the only way
371         // to really write linear types like this in Rust...
372         if let Some(conn) = &mut self.criterion.connection {
373             conn.send(&OutgoingMessage::FinishedBenchmarkGroup {
374                 group: &self.group_name,
375             })
376             .unwrap();
377 
378             conn.serve_value_formatter(self.criterion.measurement.formatter())
379                 .unwrap();
380         }
381 
382         if self.all_ids.len() > 1 && self.any_matched && self.criterion.mode.is_benchmark() {
383             let report_context = ReportContext {
384                 output_directory: self.criterion.output_directory.clone(),
385                 plot_config: self.partial_config.plot_config.clone(),
386             };
387 
388             self.criterion.report.summarize(
389                 &report_context,
390                 &self.all_ids,
391                 self.criterion.measurement.formatter(),
392             );
393         }
394         if self.any_matched {
395             self.criterion.report.group_separator();
396         }
397     }
398 }
399 
400 /// Simple structure representing an ID for a benchmark. The ID must be unique within a benchmark
401 /// group.
402 #[derive(Clone, Eq, PartialEq, Hash)]
403 pub struct BenchmarkId {
404     pub(crate) function_name: Option<String>,
405     pub(crate) parameter: Option<String>,
406 }
407 impl BenchmarkId {
408     /// Construct a new benchmark ID from a string function name and a parameter value.
409     ///
410     /// Note that the parameter value need not be the same as the parameter passed to your
411     /// actual benchmark. For instance, you might have a benchmark that takes a 1MB string as
412     /// input. It would be impractical to embed the whole string in the benchmark ID, so instead
413     /// your parameter value might be a descriptive string like "1MB Alphanumeric".
414     ///
415     /// # Examples
416     /// ```
417     /// # use criterion::{BenchmarkId, Criterion};
418     /// // A basic benchmark ID is typically constructed from a constant string and a simple
419     /// // parameter
420     /// let basic_id = BenchmarkId::new("my_id", 5);
421     ///
422     /// // The function name can be a string
423     /// let function_name = "test_string".to_string();
424     /// let string_id = BenchmarkId::new(function_name, 12);
425     ///
426     /// // Benchmark IDs are passed to benchmark groups:
427     /// let mut criterion = Criterion::default();
428     /// let mut group = criterion.benchmark_group("My Group");
429     /// // Generate a very large input
430     /// let input : String = ::std::iter::repeat("X").take(1024 * 1024).collect();
431     ///
432     /// // Note that we don't have to use the input as the parameter in the ID
433     /// group.bench_with_input(BenchmarkId::new("Test long string", "1MB X's"), &input, |b, i| {
434     ///     b.iter(|| i.len())
435     /// });
436     /// ```
new<S: Into<String>, P: ::std::fmt::Display>( function_name: S, parameter: P, ) -> BenchmarkId437     pub fn new<S: Into<String>, P: ::std::fmt::Display>(
438         function_name: S,
439         parameter: P,
440     ) -> BenchmarkId {
441         BenchmarkId {
442             function_name: Some(function_name.into()),
443             parameter: Some(format!("{}", parameter)),
444         }
445     }
446 
447     /// Construct a new benchmark ID from just a parameter value. Use this when benchmarking a
448     /// single function with a variety of different inputs.
from_parameter<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId449     pub fn from_parameter<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId {
450         BenchmarkId {
451             function_name: None,
452             parameter: Some(format!("{}", parameter)),
453         }
454     }
455 
no_function() -> BenchmarkId456     pub(crate) fn no_function() -> BenchmarkId {
457         BenchmarkId {
458             function_name: None,
459             parameter: None,
460         }
461     }
462 
no_function_with_input<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId463     pub(crate) fn no_function_with_input<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId {
464         BenchmarkId {
465             function_name: None,
466             parameter: Some(format!("{}", parameter)),
467         }
468     }
469 }
470 
471 mod private {
472     pub trait Sealed {}
473     impl Sealed for super::BenchmarkId {}
474     impl<S: Into<String>> Sealed for S {}
475 }
476 
477 /// Sealed trait which allows users to automatically convert strings to benchmark IDs.
478 pub trait IntoBenchmarkId: private::Sealed {
into_benchmark_id(self) -> BenchmarkId479     fn into_benchmark_id(self) -> BenchmarkId;
480 }
481 impl IntoBenchmarkId for BenchmarkId {
into_benchmark_id(self) -> BenchmarkId482     fn into_benchmark_id(self) -> BenchmarkId {
483         self
484     }
485 }
486 impl<S: Into<String>> IntoBenchmarkId for S {
into_benchmark_id(self) -> BenchmarkId487     fn into_benchmark_id(self) -> BenchmarkId {
488         let function_name = self.into();
489         if function_name.is_empty() {
490             panic!("Function name must not be empty.");
491         }
492 
493         BenchmarkId {
494             function_name: Some(function_name),
495             parameter: None,
496         }
497     }
498 }
499