criterion/
routine.rs

1use benchmark::BenchmarkConfig;
2use std::time::{Duration, Instant};
3
4use program::Program;
5use report::{BenchmarkId, ReportContext};
6use std::marker::PhantomData;
7use {Bencher, Criterion, DurationExt};
8
9/// PRIVATE
10pub trait Routine<T> {
11    fn start(&mut self, parameter: &T) -> Option<Program>;
12
13    /// PRIVATE
14    fn bench(&mut self, m: &mut Option<Program>, iters: &[u64], parameter: &T) -> Vec<f64>;
15    /// PRIVATE
16    fn warm_up(&mut self, m: &mut Option<Program>, how_long: Duration, parameter: &T)
17        -> (u64, u64);
18
19    /// PRIVATE
20    fn test(&mut self, parameter: &T) {
21        let mut m = self.start(parameter);
22        self.bench(&mut m, &[1u64], parameter);
23    }
24
25    /// Iterates the benchmarked function for a fixed length of time, but takes no measurements.
26    /// This keeps the overall benchmark suite runtime constant-ish even when running under a
27    /// profiler with an unknown amount of overhead. Since no measurements are taken, it also
28    /// reduces the amount of time the execution spends in Criterion.rs code, which should help
29    /// show the performance of the benchmarked code more clearly as well.
30    fn profile(
31        &mut self,
32        id: &BenchmarkId,
33        criterion: &Criterion,
34        report_context: &ReportContext,
35        time: Duration,
36        parameter: &T,
37    ) {
38        criterion
39            .report
40            .profile(id, report_context, time.to_nanos() as f64);
41
42        let time = time.to_nanos();
43        let mut m = self.start(parameter);
44
45        // Get the warmup time for one second
46        let (wu_elapsed, wu_iters) = self.warm_up(&mut m, Duration::from_secs(1), parameter);
47        if wu_elapsed >= time {
48            return;
49        }
50
51        // Initial guess for the mean execution time
52        let met = wu_elapsed as f64 / wu_iters as f64;
53
54        // Guess how many iterations will be required for the remaining time
55        let remaining = (time - wu_elapsed) as f64;
56
57        let iters = remaining / met;
58        let iters = iters as u64;
59
60        self.bench(&mut m, &[iters], parameter);
61
62        criterion.report.terminated(id, report_context);
63    }
64
65    fn sample(
66        &mut self,
67        id: &BenchmarkId,
68        config: &BenchmarkConfig,
69        criterion: &Criterion,
70        report_context: &ReportContext,
71        parameter: &T,
72    ) -> (Box<[f64]>, Box<[f64]>) {
73        let wu = config.warm_up_time;
74        let m_ns = config.measurement_time.to_nanos();
75
76        criterion
77            .report
78            .warmup(id, report_context, wu.to_nanos() as f64);
79
80        let mut m = self.start(parameter);
81
82        let (wu_elapsed, wu_iters) = self.warm_up(&mut m, wu, parameter);
83
84        // Initial guess for the mean execution time
85        let met = wu_elapsed as f64 / wu_iters as f64;
86
87        let n = config.sample_size as u64;
88        // Solve: [d + 2*d + 3*d + ... + n*d] * met = m_ns
89        let total_runs = n * (n + 1) / 2;
90        let d = (m_ns as f64 / met / total_runs as f64).ceil() as u64;
91
92        let m_iters = (1..(n + 1) as u64).map(|a| a * d).collect::<Vec<u64>>();
93
94        let m_ns = total_runs as f64 * d as f64 * met;
95        criterion
96            .report
97            .measurement_start(id, report_context, n, m_ns, m_iters.iter().sum());
98        let m_elapsed = self.bench(&mut m, &m_iters, parameter);
99
100        let m_iters_f: Vec<f64> = m_iters.iter().map(|&x| x as f64).collect();
101
102        (m_iters_f.into_boxed_slice(), m_elapsed.into_boxed_slice())
103    }
104}
105
106pub struct Function<F, T>
107where
108    F: FnMut(&mut Bencher, &T),
109{
110    f: F,
111    _phantom: PhantomData<T>,
112}
113impl<F, T> Function<F, T>
114where
115    F: FnMut(&mut Bencher, &T),
116{
117    pub fn new(f: F) -> Function<F, T> {
118        Function {
119            f,
120            _phantom: PhantomData,
121        }
122    }
123}
124
125impl<F, T> Routine<T> for Function<F, T>
126where
127    F: FnMut(&mut Bencher, &T),
128{
129    fn start(&mut self, _: &T) -> Option<Program> {
130        None
131    }
132
133    fn bench(&mut self, _: &mut Option<Program>, iters: &[u64], parameter: &T) -> Vec<f64> {
134        let f = &mut self.f;
135
136        let mut b = Bencher {
137            iterated: false,
138            iters: 0,
139            elapsed: Duration::from_secs(0),
140        };
141
142        iters
143            .iter()
144            .map(|iters| {
145                b.iters = *iters;
146                (*f)(&mut b, parameter);
147                b.assert_iterated();
148                b.elapsed.to_nanos() as f64
149            })
150            .collect()
151    }
152
153    fn warm_up(
154        &mut self,
155        _: &mut Option<Program>,
156        how_long: Duration,
157        parameter: &T,
158    ) -> (u64, u64) {
159        let f = &mut self.f;
160        let mut b = Bencher {
161            iterated: false,
162            iters: 1,
163            elapsed: Duration::from_secs(0),
164        };
165
166        let mut total_iters = 0;
167        let start = Instant::now();
168        loop {
169            (*f)(&mut b, parameter);
170
171            b.assert_iterated();
172
173            total_iters += b.iters;
174            let elapsed = start.elapsed();
175            if elapsed > how_long {
176                return (elapsed.to_nanos(), total_iters);
177            }
178
179            b.iters *= 2;
180        }
181    }
182}