1 use crate::benchmark::BenchmarkConfig;
2 use crate::connection::OutgoingMessage;
3 use crate::measurement::Measurement;
4 use crate::report::{BenchmarkId, Report, ReportContext};
5 use crate::{ActualSamplingMode, Bencher, Criterion, DurationExt};
6 use std::marker::PhantomData;
7 use std::time::Duration;
8 
9 /// PRIVATE
10 pub(crate) trait Routine<M: Measurement, T: ?Sized> {
11     /// PRIVATE
bench(&mut self, m: &M, iters: &[u64], parameter: &T) -> Vec<f64>12     fn bench(&mut self, m: &M, iters: &[u64], parameter: &T) -> Vec<f64>;
13     /// PRIVATE
warm_up(&mut self, m: &M, how_long: Duration, parameter: &T) -> (u64, u64)14     fn warm_up(&mut self, m: &M, how_long: Duration, parameter: &T) -> (u64, u64);
15 
16     /// PRIVATE
test(&mut self, m: &M, parameter: &T)17     fn test(&mut self, m: &M, parameter: &T) {
18         self.bench(m, &[1u64], parameter);
19     }
20 
21     /// Iterates the benchmarked function for a fixed length of time, but takes no measurements.
22     /// This keeps the overall benchmark suite runtime constant-ish even when running under a
23     /// profiler with an unknown amount of overhead. Since no measurements are taken, it also
24     /// reduces the amount of time the execution spends in Criterion.rs code, which should help
25     /// show the performance of the benchmarked code more clearly as well.
profile( &mut self, measurement: &M, id: &BenchmarkId, criterion: &Criterion<M>, report_context: &ReportContext, time: Duration, parameter: &T, )26     fn profile(
27         &mut self,
28         measurement: &M,
29         id: &BenchmarkId,
30         criterion: &Criterion<M>,
31         report_context: &ReportContext,
32         time: Duration,
33         parameter: &T,
34     ) {
35         criterion
36             .report
37             .profile(id, report_context, time.to_nanos() as f64);
38 
39         let mut profile_path = report_context.output_directory.clone();
40         if (*crate::CARGO_CRITERION_CONNECTION).is_some() {
41             // If connected to cargo-criterion, generate a cargo-criterion-style path.
42             // This is kind of a hack.
43             profile_path.push("profile");
44             profile_path.push(id.as_directory_name());
45         } else {
46             profile_path.push(id.as_directory_name());
47             profile_path.push("profile");
48         }
49         criterion
50             .profiler
51             .borrow_mut()
52             .start_profiling(id.id(), &profile_path);
53 
54         let time = time.to_nanos();
55 
56         // TODO: Some profilers will show the two batches of iterations as
57         // being different code-paths even though they aren't really.
58 
59         // Get the warmup time for one second
60         let (wu_elapsed, wu_iters) = self.warm_up(measurement, Duration::from_secs(1), parameter);
61         if wu_elapsed < time {
62             // Initial guess for the mean execution time
63             let met = wu_elapsed as f64 / wu_iters as f64;
64 
65             // Guess how many iterations will be required for the remaining time
66             let remaining = (time - wu_elapsed) as f64;
67 
68             let iters = remaining / met;
69             let iters = iters as u64;
70 
71             self.bench(measurement, &[iters], parameter);
72         }
73 
74         criterion
75             .profiler
76             .borrow_mut()
77             .stop_profiling(id.id(), &profile_path);
78 
79         criterion.report.terminated(id, report_context);
80     }
81 
sample( &mut self, measurement: &M, id: &BenchmarkId, config: &BenchmarkConfig, criterion: &Criterion<M>, report_context: &ReportContext, parameter: &T, ) -> (ActualSamplingMode, Box<[f64]>, Box<[f64]>)82     fn sample(
83         &mut self,
84         measurement: &M,
85         id: &BenchmarkId,
86         config: &BenchmarkConfig,
87         criterion: &Criterion<M>,
88         report_context: &ReportContext,
89         parameter: &T,
90     ) -> (ActualSamplingMode, Box<[f64]>, Box<[f64]>) {
91         let wu = config.warm_up_time;
92         let m_ns = config.measurement_time.to_nanos();
93 
94         criterion
95             .report
96             .warmup(id, report_context, wu.to_nanos() as f64);
97 
98         if let Some(conn) = &criterion.connection {
99             conn.send(&OutgoingMessage::Warmup {
100                 id: id.into(),
101                 nanos: wu.to_nanos() as f64,
102             })
103             .unwrap();
104         }
105 
106         let (wu_elapsed, wu_iters) = self.warm_up(measurement, wu, parameter);
107         if crate::debug_enabled() {
108             println!(
109                 "\nCompleted {} iterations in {} nanoseconds, estimated execution time is {} ns",
110                 wu_iters,
111                 wu_elapsed,
112                 wu_elapsed as f64 / wu_iters as f64
113             );
114         }
115 
116         // Initial guess for the mean execution time
117         let met = wu_elapsed as f64 / wu_iters as f64;
118 
119         let n = config.sample_size as u64;
120 
121         let actual_sampling_mode = config
122             .sampling_mode
123             .choose_sampling_mode(met, n, m_ns as f64);
124 
125         let m_iters = actual_sampling_mode.iteration_counts(met, n, &config.measurement_time);
126 
127         let expected_ns = m_iters
128             .iter()
129             .copied()
130             .map(|count| count as f64 * met)
131             .sum();
132 
133         // Use saturating_add to handle overflow.
134         let mut total_iters = 0u64;
135         for count in m_iters.iter().copied() {
136             total_iters = total_iters.saturating_add(count);
137         }
138 
139         criterion
140             .report
141             .measurement_start(id, report_context, n, expected_ns, total_iters);
142 
143         if let Some(conn) = &criterion.connection {
144             conn.send(&OutgoingMessage::MeasurementStart {
145                 id: id.into(),
146                 sample_count: n,
147                 estimate_ns: expected_ns,
148                 iter_count: total_iters,
149             })
150             .unwrap();
151         }
152 
153         let m_elapsed = self.bench(measurement, &m_iters, parameter);
154 
155         let m_iters_f: Vec<f64> = m_iters.iter().map(|&x| x as f64).collect();
156 
157         (
158             actual_sampling_mode,
159             m_iters_f.into_boxed_slice(),
160             m_elapsed.into_boxed_slice(),
161         )
162     }
163 }
164 
165 pub struct Function<M: Measurement, F, T>
166 where
167     F: FnMut(&mut Bencher<'_, M>, &T),
168     T: ?Sized,
169 {
170     f: F,
171     // TODO: Is there some way to remove these?
172     _phantom: PhantomData<T>,
173     _phamtom2: PhantomData<M>,
174 }
175 impl<M: Measurement, F, T> Function<M, F, T>
176 where
177     F: FnMut(&mut Bencher<'_, M>, &T),
178     T: ?Sized,
179 {
new(f: F) -> Function<M, F, T>180     pub fn new(f: F) -> Function<M, F, T> {
181         Function {
182             f,
183             _phantom: PhantomData,
184             _phamtom2: PhantomData,
185         }
186     }
187 }
188 
189 impl<M: Measurement, F, T> Routine<M, T> for Function<M, F, T>
190 where
191     F: FnMut(&mut Bencher<'_, M>, &T),
192     T: ?Sized,
193 {
bench(&mut self, m: &M, iters: &[u64], parameter: &T) -> Vec<f64>194     fn bench(&mut self, m: &M, iters: &[u64], parameter: &T) -> Vec<f64> {
195         let f = &mut self.f;
196 
197         let mut b = Bencher {
198             iterated: false,
199             iters: 0,
200             value: m.zero(),
201             measurement: m,
202             elapsed_time: Duration::from_millis(0),
203         };
204 
205         iters
206             .iter()
207             .map(|iters| {
208                 b.iters = *iters;
209                 (*f)(&mut b, parameter);
210                 b.assert_iterated();
211                 m.to_f64(&b.value)
212             })
213             .collect()
214     }
215 
warm_up(&mut self, m: &M, how_long: Duration, parameter: &T) -> (u64, u64)216     fn warm_up(&mut self, m: &M, how_long: Duration, parameter: &T) -> (u64, u64) {
217         let f = &mut self.f;
218         let mut b = Bencher {
219             iterated: false,
220             iters: 1,
221             value: m.zero(),
222             measurement: m,
223             elapsed_time: Duration::from_millis(0),
224         };
225 
226         let mut total_iters = 0;
227         let mut elapsed_time = Duration::from_millis(0);
228         loop {
229             (*f)(&mut b, parameter);
230 
231             b.assert_iterated();
232 
233             total_iters += b.iters;
234             elapsed_time += b.elapsed_time;
235             if elapsed_time > how_long {
236                 return (elapsed_time.to_nanos(), total_iters);
237             }
238 
239             b.iters = b.iters.wrapping_mul(2);
240         }
241     }
242 }
243