1use crate::BenchmarkResult;
21use std::collections::BTreeMap;
22
23pub struct Analysis {
24 pub base: u128,
25 pub slopes: Vec<u128>,
26 pub names: Vec<String>,
27 pub value_dists: Option<Vec<(Vec<u32>, u128, u128)>>,
28 pub errors: Option<Vec<u128>>,
29 pub minimum: u128,
30 selector: BenchmarkSelector,
31}
32
33#[derive(Clone, Copy)]
34pub enum BenchmarkSelector {
35 ExtrinsicTime,
36 StorageRootTime,
37 Reads,
38 Writes,
39 ProofSize,
40}
41
42fn mul_1000_into_u128(value: f64) -> u128 {
44 (value as u128)
46 .saturating_mul(1000)
47 .saturating_add((value.fract() * 1000.0) as u128)
48}
49
50impl BenchmarkSelector {
51 fn scale_and_cast_weight(self, value: f64, round_up: bool) -> u128 {
52 if let BenchmarkSelector::ExtrinsicTime = self {
53 mul_1000_into_u128(value + 0.000_000_005)
57 } else {
58 if round_up {
59 (value + 0.5) as u128
60 } else {
61 value as u128
62 }
63 }
64 }
65
66 fn scale_weight(self, value: u128) -> u128 {
67 if let BenchmarkSelector::ExtrinsicTime = self {
68 value.saturating_mul(1000)
69 } else {
70 value
71 }
72 }
73
74 fn nanos_from_weight(self, value: u128) -> u128 {
75 if let BenchmarkSelector::ExtrinsicTime = self {
76 value / 1000
77 } else {
78 value
79 }
80 }
81
82 fn get_value(self, result: &BenchmarkResult) -> u128 {
83 match self {
84 BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
85 BenchmarkSelector::StorageRootTime => result.storage_root_time,
86 BenchmarkSelector::Reads => result.reads.into(),
87 BenchmarkSelector::Writes => result.writes.into(),
88 BenchmarkSelector::ProofSize => result.proof_size.into(),
89 }
90 }
91
92 fn get_minimum(self, results: &[BenchmarkResult]) -> u128 {
93 results
94 .iter()
95 .map(|result| self.get_value(result))
96 .min()
97 .expect("results cannot be empty")
98 }
99}
100
101#[derive(Debug)]
102pub enum AnalysisChoice {
103 MinSquares,
105 MedianSlopes,
107 Max,
109}
110
111impl Default for AnalysisChoice {
112 fn default() -> Self {
113 AnalysisChoice::MinSquares
114 }
115}
116
117impl TryFrom<Option<String>> for AnalysisChoice {
118 type Error = &'static str;
119
120 fn try_from(s: Option<String>) -> Result<Self, Self::Error> {
121 match s {
122 None => Ok(AnalysisChoice::default()),
123 Some(i) => match &i[..] {
124 "min-squares" | "min_squares" => Ok(AnalysisChoice::MinSquares),
125 "median-slopes" | "median_slopes" => Ok(AnalysisChoice::MedianSlopes),
126 "max" => Ok(AnalysisChoice::Max),
127 _ => Err("invalid analysis string"),
128 },
129 }
130 }
131}
132
133fn raw_linear_regression(
134 xs: &[f64],
135 ys: &[f64],
136 x_vars: usize,
137 with_intercept: bool,
138) -> Option<(f64, Vec<f64>, Vec<f64>)> {
139 let mut data: Vec<f64> = Vec::new();
140
141 for (&y, xs) in ys.iter().zip(xs.chunks_exact(x_vars)) {
155 data.push(y);
156 if with_intercept {
157 data.push(1.0);
158 } else {
159 data.push(0.0);
160 }
161 data.extend(xs);
162 }
163 let model = linregress::fit_low_level_regression_model(&data, ys.len(), x_vars + 2).ok()?;
164 Some((model.parameters()[0], model.parameters()[1..].to_vec(), model.se().to_vec()))
165}
166
167fn linear_regression(
168 xs: Vec<f64>,
169 mut ys: Vec<f64>,
170 x_vars: usize,
171) -> Option<(f64, Vec<f64>, Vec<f64>)> {
172 let (intercept, params, errors) = raw_linear_regression(&xs, &ys, x_vars, true)?;
173 if intercept >= -0.0001 {
174 return Some((intercept, params, errors[1..].to_vec()));
176 }
177
178 let mut min = ys[0];
182 for &value in &ys {
183 if value < min {
184 min = value;
185 }
186 }
187
188 for value in &mut ys {
189 *value -= min;
190 }
191
192 let (intercept, params, errors) = raw_linear_regression(&xs, &ys, x_vars, false)?;
193 assert!(intercept.abs() <= 0.0001);
194 Some((min, params, errors[1..].to_vec()))
195}
196
197impl Analysis {
198 fn median_value(
201 r: &Vec<BenchmarkResult>,
202 selector: BenchmarkSelector,
203 ) -> Result<Self, anyhow::Error> {
204 anyhow::ensure!(!r.is_empty(), "benchmark results cannot be empty");
205
206 let mut values: Vec<u128> = r
207 .iter()
208 .map(|result| match selector {
209 BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
210 BenchmarkSelector::StorageRootTime => result.storage_root_time,
211 BenchmarkSelector::Reads => result.reads.into(),
212 BenchmarkSelector::Writes => result.writes.into(),
213 BenchmarkSelector::ProofSize => result.proof_size.into(),
214 })
215 .collect();
216
217 values.sort();
218 let mid = values.len() / 2;
219
220 Ok(Self {
221 base: selector.scale_weight(values[mid]),
222 slopes: Vec::new(),
223 names: Vec::new(),
224 value_dists: None,
225 errors: None,
226 minimum: selector.get_minimum(&r),
227 selector,
228 })
229 }
230
231 pub fn median_slopes(
232 r: &Vec<BenchmarkResult>,
233 selector: BenchmarkSelector,
234 ) -> Result<Self, anyhow::Error> {
235 anyhow::ensure!(!r.is_empty(), "benchmark results cannot be empty");
236
237 if r[0].components.is_empty() {
238 return Self::median_value(r, selector);
239 }
240
241 let results = r[0]
242 .components
243 .iter()
244 .enumerate()
245 .map(|(i, &(param, _))| {
246 let mut counted = BTreeMap::<Vec<u32>, usize>::new();
247 for result in r.iter() {
248 let mut p = result.components.iter().map(|x| x.1).collect::<Vec<_>>();
249 p[i] = 0;
250 *counted.entry(p).or_default() += 1;
251 }
252 let others: Vec<u32> =
253 counted.iter().max_by_key(|i| i.1).expect("r is not empty; qed").0.clone();
254 let values = r
255 .iter()
256 .filter(|v| {
257 v.components
258 .iter()
259 .map(|x| x.1)
260 .zip(others.iter())
261 .enumerate()
262 .all(|(j, (v1, v2))| j == i || v1 == *v2)
263 })
264 .map(|result| {
265 let data = match selector {
267 BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
268 BenchmarkSelector::StorageRootTime => result.storage_root_time,
269 BenchmarkSelector::Reads => result.reads.into(),
270 BenchmarkSelector::Writes => result.writes.into(),
271 BenchmarkSelector::ProofSize => result.proof_size.into(),
272 };
273 (result.components[i].1, data)
274 })
275 .collect::<Vec<_>>();
276 (format!("{:?}", param), i, others, values)
277 })
278 .collect::<Vec<_>>();
279
280 let models = results
281 .iter()
282 .map(|(param_name, _, _, ref values)| {
283 let mut slopes = vec![];
284 for (i, &(x1, y1)) in values.iter().enumerate() {
285 for &(x2, y2) in values.iter().skip(i + 1) {
286 if x1 != x2 {
287 slopes.push((y1 as f64 - y2 as f64) / (x1 as f64 - x2 as f64));
288 }
289 }
290 }
291 if slopes.is_empty() {
292 let unique_values = values
293 .iter()
294 .map(|(x, _)| x)
295 .collect::<std::collections::BTreeSet<_>>()
296 .len();
297 return Err(anyhow::anyhow!(
298 "Parameter `{param_name}` only has \
299 {unique_values} unique value(s) but needs at least 2 to compute a slope. \
300 This can happen when too many benchmark samples are skipped. \
301 Try increasing the number of steps for this parameter or fix the benchmark.",
302 ));
303 }
304 slopes.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
305 let slope = slopes[slopes.len() / 2];
306
307 let mut offsets = vec![];
308 for &(x, y) in values.iter() {
309 offsets.push(y as f64 - slope * x as f64);
310 }
311 offsets.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
312 let offset = offsets[offsets.len() / 2];
313
314 Ok((offset, slope))
315 })
316 .collect::<Result<Vec<_>, anyhow::Error>>()?;
317
318 let models = models
319 .iter()
320 .zip(results.iter())
321 .map(|((offset, slope), (_, i, others, _))| {
322 let over = others
323 .iter()
324 .enumerate()
325 .filter(|(j, _)| j != i)
326 .map(|(j, v)| models[j].1 * *v as f64)
327 .fold(0f64, |acc, i| acc + i);
328 (*offset - over, *slope)
329 })
330 .collect::<Vec<_>>();
331
332 let base = selector.scale_and_cast_weight(models[0].0.max(0f64), false);
333 let slopes = models
334 .iter()
335 .map(|x| selector.scale_and_cast_weight(x.1.max(0f64), false))
336 .collect::<Vec<_>>();
337
338 Ok(Self {
339 base,
340 slopes,
341 names: results.into_iter().map(|x| x.0).collect::<Vec<_>>(),
342 value_dists: None,
343 errors: None,
344 minimum: selector.get_minimum(&r),
345 selector,
346 })
347 }
348
349 pub fn min_squares_iqr(
350 r: &Vec<BenchmarkResult>,
351 selector: BenchmarkSelector,
352 ) -> Result<Self, anyhow::Error> {
353 anyhow::ensure!(!r.is_empty(), "benchmark results cannot be empty");
354
355 if r[0].components.is_empty() {
356 return Self::median_value(r, selector);
357 }
358
359 if r.len() <= 2 {
363 return Self::median_slopes(r, selector);
364 }
365
366 let mut results = BTreeMap::<Vec<u32>, Vec<u128>>::new();
367 for result in r.iter() {
368 let p = result.components.iter().map(|x| x.1).collect::<Vec<_>>();
369 results.entry(p).or_default().push(match selector {
370 BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
371 BenchmarkSelector::StorageRootTime => result.storage_root_time,
372 BenchmarkSelector::Reads => result.reads.into(),
373 BenchmarkSelector::Writes => result.writes.into(),
374 BenchmarkSelector::ProofSize => result.proof_size.into(),
375 })
376 }
377
378 for (_, rs) in results.iter_mut() {
379 rs.sort();
380 let ql = rs.len() / 4;
381 *rs = rs[ql..rs.len() - ql].to_vec();
382 }
383
384 let names = r[0].components.iter().map(|x| format!("{:?}", x.0)).collect::<Vec<_>>();
385 let value_dists = results
386 .iter()
387 .map(|(p, vs)| {
388 if vs.is_empty() {
390 return (p.clone(), 0, 0);
391 }
392 let total = vs.iter().fold(0u128, |acc, v| acc + *v);
393 let mean = total / vs.len() as u128;
394 let sum_sq_diff = vs.iter().fold(0u128, |acc, v| {
395 let d = mean.max(*v) - mean.min(*v);
396 acc + d * d
397 });
398 let stddev = (sum_sq_diff as f64 / vs.len() as f64).sqrt() as u128;
399 (p.clone(), mean, stddev)
400 })
401 .collect::<Vec<_>>();
402
403 let mut ys: Vec<f64> = Vec::new();
404 let mut xs: Vec<f64> = Vec::new();
405 for result in results {
406 let x: Vec<f64> = result.0.iter().map(|value| *value as f64).collect();
407 for y in result.1 {
408 xs.extend(x.iter().copied());
409 ys.push(y as f64);
410 }
411 }
412
413 let (intercept, slopes, errors) = linear_regression(xs, ys, r[0].components.len())
414 .ok_or_else(|| {
415 anyhow::anyhow!("linear regression failed for min_squares_iqr analysis")
416 })?;
417
418 Ok(Self {
419 base: selector.scale_and_cast_weight(intercept, true),
420 slopes: slopes
421 .into_iter()
422 .map(|value| selector.scale_and_cast_weight(value, true))
423 .collect(),
424 names,
425 value_dists: Some(value_dists),
426 errors: Some(
427 errors
428 .into_iter()
429 .map(|value| selector.scale_and_cast_weight(value, false))
430 .collect(),
431 ),
432 minimum: selector.get_minimum(&r),
433 selector,
434 })
435 }
436
437 pub fn max(
438 r: &Vec<BenchmarkResult>,
439 selector: BenchmarkSelector,
440 ) -> Result<Self, anyhow::Error> {
441 let median_slopes = Self::median_slopes(r, selector)?;
442 let min_squares = Self::min_squares_iqr(r, selector)?;
443
444 let base = median_slopes.base.max(min_squares.base);
445 let slopes = median_slopes
446 .slopes
447 .into_iter()
448 .zip(min_squares.slopes.into_iter())
449 .map(|(a, b): (u128, u128)| a.max(b))
450 .collect::<Vec<u128>>();
451 median_slopes
453 .names
454 .iter()
455 .zip(min_squares.names.iter())
456 .for_each(|(a, b)| assert!(a == b, "benchmark results not in the same order"));
457 let names = median_slopes.names;
458 let value_dists = min_squares.value_dists;
459 let errors = min_squares.errors;
460 let minimum = selector.get_minimum(&r);
461
462 Ok(Self { base, slopes, names, value_dists, errors, selector, minimum })
463 }
464}
465
466fn ms(mut nanos: u128) -> String {
467 let mut x = 100_000u128;
468 while x > 1 {
469 if nanos > x * 1_000 {
470 nanos = nanos / x * x;
471 break;
472 }
473 x /= 10;
474 }
475 format!("{}", nanos as f64 / 1_000f64)
476}
477
478impl std::fmt::Display for Analysis {
479 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
480 if let Some(ref value_dists) = self.value_dists {
481 writeln!(f, "\nData points distribution:")?;
482 writeln!(
483 f,
484 "{} mean µs sigma µs %",
485 self.names.iter().map(|p| format!("{:>5}", p)).collect::<Vec<_>>().join(" ")
486 )?;
487 for (param_values, mean, sigma) in value_dists.iter() {
488 if *mean == 0 {
489 writeln!(
490 f,
491 "{} {:>8} {:>8} {:>3}.{}%",
492 param_values
493 .iter()
494 .map(|v| format!("{:>5}", v))
495 .collect::<Vec<_>>()
496 .join(" "),
497 ms(*mean),
498 ms(*sigma),
499 "?",
500 "?"
501 )?;
502 } else {
503 writeln!(
504 f,
505 "{} {:>8} {:>8} {:>3}.{}%",
506 param_values
507 .iter()
508 .map(|v| format!("{:>5}", v))
509 .collect::<Vec<_>>()
510 .join(" "),
511 ms(*mean),
512 ms(*sigma),
513 (sigma * 100 / mean),
514 (sigma * 1000 / mean % 10)
515 )?;
516 }
517 }
518 }
519
520 if let Some(ref errors) = self.errors {
521 writeln!(f, "\nQuality and confidence:")?;
522 writeln!(f, "param error")?;
523 for (p, se) in self.names.iter().zip(errors.iter()) {
524 writeln!(f, "{} {:>8}", p, ms(self.selector.nanos_from_weight(*se)))?;
525 }
526 }
527
528 writeln!(f, "\nModel:")?;
529 writeln!(f, "Time ~= {:>8}", ms(self.selector.nanos_from_weight(self.base)))?;
530 for (&t, n) in self.slopes.iter().zip(self.names.iter()) {
531 writeln!(f, " + {} {:>8}", n, ms(self.selector.nanos_from_weight(t)))?;
532 }
533 writeln!(f, " µs")
534 }
535}
536
537impl std::fmt::Debug for Analysis {
538 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
539 write!(f, "{}", self.base)?;
540 for (&m, n) in self.slopes.iter().zip(self.names.iter()) {
541 write!(f, " + ({} * {})", m, n)?;
542 }
543 write!(f, "")
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use crate::BenchmarkParameter;
551
552 fn benchmark_result(
553 components: Vec<(BenchmarkParameter, u32)>,
554 extrinsic_time: u128,
555 storage_root_time: u128,
556 reads: u32,
557 writes: u32,
558 ) -> BenchmarkResult {
559 BenchmarkResult {
560 components,
561 extrinsic_time,
562 storage_root_time,
563 reads,
564 repeat_reads: 0,
565 writes,
566 repeat_writes: 0,
567 proof_size: 0,
568 keys: vec![],
569 }
570 }
571
572 #[test]
573 fn test_linear_regression() {
574 let ys = vec![
575 3797981.0,
576 37857779.0,
577 70569402.0,
578 104004114.0,
579 137233924.0,
580 169826237.0,
581 203521133.0,
582 237552333.0,
583 271082065.0,
584 305554637.0,
585 335218347.0,
586 371759065.0,
587 405086197.0,
588 438353555.0,
589 472891417.0,
590 505339532.0,
591 527784778.0,
592 562590596.0,
593 635291991.0,
594 673027090.0,
595 708119408.0,
596 ];
597 let xs = vec![
598 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
599 16.0, 17.0, 18.0, 19.0, 20.0,
600 ];
601
602 let (intercept, params, errors) = raw_linear_regression(&xs, &ys, 1, true).unwrap();
603 assert_eq!(intercept as i64, -2712997);
604 assert_eq!(params.len(), 1);
605 assert_eq!(params[0] as i64, 34444926);
606 assert_eq!(errors.len(), 2);
607 assert_eq!(errors[0] as i64, 4805766);
608 assert_eq!(errors[1] as i64, 411084);
609
610 let (intercept, params, errors) = linear_regression(xs, ys, 1).unwrap();
611 assert_eq!(intercept as i64, 3797981);
612 assert_eq!(params.len(), 1);
613 assert_eq!(params[0] as i64, 33968513);
614 assert_eq!(errors.len(), 1);
615 assert_eq!(errors[0] as i64, 217331);
616 }
617
618 #[test]
626 fn min_squares_iqr_fits_slope_with_two_distinct_x_samples() {
627 let data = vec![
629 benchmark_result(vec![(BenchmarkParameter::n, 4)], 0, 0, 8, 0),
630 benchmark_result(vec![(BenchmarkParameter::n, 8)], 0, 0, 12, 0),
631 ];
632 let analysis = Analysis::min_squares_iqr(&data, BenchmarkSelector::Reads).unwrap();
633 assert_eq!(analysis.slopes, vec![1]);
634 assert_eq!(analysis.base, 4);
635 }
636
637 #[test]
641 fn min_squares_iqr_two_samples_same_x_errors() {
642 let data = vec![
643 benchmark_result(vec![(BenchmarkParameter::n, 4)], 0, 0, 8, 0),
644 benchmark_result(vec![(BenchmarkParameter::n, 4)], 0, 0, 10, 0),
645 ];
646 let err = Analysis::min_squares_iqr(&data, BenchmarkSelector::Reads).unwrap_err();
647 assert!(
648 err.to_string().contains("only has 1 unique value"),
649 "expected 'only has 1 unique value' diagnostic, got: {err}",
650 );
651 }
652
653 #[test]
654 fn analysis_median_slopes_should_work() {
655 let data = vec![
656 benchmark_result(
657 vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)],
658 11_500_000,
659 0,
660 3,
661 10,
662 ),
663 benchmark_result(
664 vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)],
665 12_500_000,
666 0,
667 4,
668 10,
669 ),
670 benchmark_result(
671 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)],
672 13_500_000,
673 0,
674 5,
675 10,
676 ),
677 benchmark_result(
678 vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)],
679 14_500_000,
680 0,
681 6,
682 10,
683 ),
684 benchmark_result(
685 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)],
686 13_100_000,
687 0,
688 5,
689 2,
690 ),
691 benchmark_result(
692 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)],
693 13_300_000,
694 0,
695 5,
696 6,
697 ),
698 benchmark_result(
699 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)],
700 13_700_000,
701 0,
702 5,
703 14,
704 ),
705 benchmark_result(
706 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)],
707 14_000_000,
708 0,
709 5,
710 20,
711 ),
712 ];
713
714 let extrinsic_time =
715 Analysis::median_slopes(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
716 assert_eq!(extrinsic_time.base, 10_000_000_000);
717 assert_eq!(extrinsic_time.slopes, vec![1_000_000_000, 100_000_000]);
718
719 let reads = Analysis::median_slopes(&data, BenchmarkSelector::Reads).unwrap();
720 assert_eq!(reads.base, 2);
721 assert_eq!(reads.slopes, vec![1, 0]);
722
723 let writes = Analysis::median_slopes(&data, BenchmarkSelector::Writes).unwrap();
724 assert_eq!(writes.base, 0);
725 assert_eq!(writes.slopes, vec![0, 2]);
726 }
727
728 #[test]
729 fn analysis_median_min_squares_should_work() {
730 let data = vec![
731 benchmark_result(
732 vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)],
733 11_500_000,
734 0,
735 3,
736 10,
737 ),
738 benchmark_result(
739 vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)],
740 12_500_000,
741 0,
742 4,
743 10,
744 ),
745 benchmark_result(
746 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)],
747 13_500_000,
748 0,
749 5,
750 10,
751 ),
752 benchmark_result(
753 vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)],
754 14_500_000,
755 0,
756 6,
757 10,
758 ),
759 benchmark_result(
760 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)],
761 13_100_000,
762 0,
763 5,
764 2,
765 ),
766 benchmark_result(
767 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)],
768 13_300_000,
769 0,
770 5,
771 6,
772 ),
773 benchmark_result(
774 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)],
775 13_700_000,
776 0,
777 5,
778 14,
779 ),
780 benchmark_result(
781 vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)],
782 14_000_000,
783 0,
784 5,
785 20,
786 ),
787 ];
788
789 let extrinsic_time =
790 Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
791 assert_eq!(extrinsic_time.base, 10_000_000_000);
792 assert_eq!(extrinsic_time.slopes, vec![1000000000, 100000000]);
793
794 let reads = Analysis::min_squares_iqr(&data, BenchmarkSelector::Reads).unwrap();
795 assert_eq!(reads.base, 2);
796 assert_eq!(reads.slopes, vec![1, 0]);
797
798 let writes = Analysis::min_squares_iqr(&data, BenchmarkSelector::Writes).unwrap();
799 assert_eq!(writes.base, 0);
800 assert_eq!(writes.slopes, vec![0, 2]);
801 }
802
803 #[test]
804 fn analysis_min_squares_iqr_uses_multiple_samples_for_same_parameters() {
805 let data = vec![
806 benchmark_result(vec![(BenchmarkParameter::n, 0)], 2_000_000, 0, 0, 0),
807 benchmark_result(vec![(BenchmarkParameter::n, 0)], 4_000_000, 0, 0, 0),
808 benchmark_result(vec![(BenchmarkParameter::n, 1)], 4_000_000, 0, 0, 0),
809 benchmark_result(vec![(BenchmarkParameter::n, 1)], 8_000_000, 0, 0, 0),
810 ];
811
812 let extrinsic_time =
813 Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
814 assert_eq!(extrinsic_time.base, 3_000_000_000);
815 assert_eq!(extrinsic_time.slopes, vec![3_000_000_000]);
816 }
817
818 #[test]
819 fn intercept_of_a_little_under_zero_is_rounded_up_to_zero() {
820 let data = vec![
824 benchmark_result(vec![(BenchmarkParameter::n, 1)], 2, 0, 0, 0),
825 benchmark_result(vec![(BenchmarkParameter::n, 2)], 4, 0, 0, 0),
826 benchmark_result(vec![(BenchmarkParameter::n, 3)], 6, 0, 0, 0),
827 ];
828
829 let extrinsic_time =
830 Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
831 assert_eq!(extrinsic_time.base, 0);
832 assert_eq!(extrinsic_time.slopes, vec![2000]);
833 }
834}