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