1use super::batch_size::BatchSize;
4use super::engine::SampleRaw;
5use super::state::Bencher;
6use crate::measure::Timer;
7use std::hint::black_box;
8
9impl Bencher {
10 #[inline]
14 pub fn iter_with_setup<T, S, F, R>(&mut self, setup: S, routine: F)
15 where
16 S: FnMut() -> T,
17 F: FnMut(T) -> R,
18 {
19 self.iter_batched(setup, routine, BatchSize::SmallInput);
20 }
21
22 #[inline]
28 pub fn iter_batched<T, S, F, R>(&mut self, setup: S, mut routine: F, size: BatchSize)
29 where
30 S: FnMut() -> T,
31 F: FnMut(T) -> R,
32 {
33 self.run_batched(size, setup, |inputs: &mut Vec<T>, outputs: &mut Vec<R>| {
34 for input in inputs.drain(..) {
35 outputs.push(black_box(routine(input)));
36 }
37 });
38 }
39
40 #[inline]
45 pub fn iter_batched_ref<T, S, F, R>(&mut self, setup: S, mut routine: F, size: BatchSize)
46 where
47 S: FnMut() -> T,
48 F: FnMut(&mut T) -> R,
49 {
50 self.run_batched(size, setup, |inputs: &mut Vec<T>, outputs: &mut Vec<R>| {
51 for input in inputs.iter_mut() {
52 outputs.push(black_box(routine(input)));
53 }
54 });
55 }
56
57 #[inline]
62 pub fn iter_batched_shared<T, S, F, R>(&mut self, batch_size: u64, mut setup: S, mut routine: F)
63 where
64 S: FnMut() -> T,
65 F: FnMut(&T) -> R,
66 {
67 if !self.begin_iter() {
68 return;
69 }
70 let batch_size = batch_size.max(1);
71 self.run_loop(|b, n| {
72 let mut raw = SampleRaw::default();
73 let mut remaining = n;
74 while remaining > 0 {
75 let chunk = remaining.min(batch_size);
76 let input = setup();
77
78 b.alloc_reset();
79 let timer = Timer::start();
80 for _ in 0..chunk {
81 black_box(routine(black_box(&input)));
82 }
83 let (ns, cycles) = timer.stop();
84 raw.add(ns, cycles, b.alloc_read());
85
86 drop(input);
87 remaining -= chunk;
88 }
89 raw
90 });
91 }
92
93 fn run_batched<T, R, S, B>(&mut self, size: BatchSize, mut setup: S, mut timed: B)
99 where
100 S: FnMut() -> T,
101 B: FnMut(&mut Vec<T>, &mut Vec<R>),
102 {
103 if !self.begin_iter() {
104 return;
105 }
106 let mut inputs: Vec<T> = Vec::new();
107 let mut outputs: Vec<R> = Vec::new();
108 self.run_loop(|b, n| {
109 let mut raw = SampleRaw::default();
110 let batch_len = size.batch_len(n);
111 let mut remaining = n;
112 while remaining > 0 {
113 let chunk = remaining.min(batch_len);
114 let len = usize::try_from(chunk).unwrap_or(usize::MAX);
116 inputs.reserve(len);
117 outputs.reserve(len);
118 inputs.extend((0..chunk).map(|_| setup()));
119
120 b.alloc_reset();
121 let timer = Timer::start();
122 timed(&mut inputs, &mut outputs);
123 let (ns, cycles) = timer.stop();
124 raw.add(ns, cycles, b.alloc_read());
125
126 outputs.clear();
127 inputs.clear();
128 remaining -= chunk;
129 }
130 raw
131 });
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use crate::bencher::test_support::{fixed, quick};
138 use crate::bencher::{BatchSize, Bencher, MeasureConfig, run_benchmark_loop};
139 use std::cell::Cell;
140 use std::time::Duration;
141
142 #[derive(Default)]
144 struct Trace {
145 setups: Cell<u64>,
146 calls: Cell<u64>,
147 batches: Cell<u64>,
148 in_setup: Cell<bool>,
149 }
150
151 impl Trace {
152 fn setup(&self) {
153 if !self.in_setup.replace(true) {
154 self.batches.set(self.batches.get() + 1);
155 }
156 self.setups.set(self.setups.get() + 1);
157 }
158
159 fn call(&self) {
160 self.in_setup.set(false);
161 self.calls.set(self.calls.get() + 1);
162 }
163 }
164
165 fn trace_by_value(size: BatchSize) -> Trace {
167 let trace = Trace::default();
168 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 40)), |b| {
169 b.iter_batched(|| trace.setup(), |()| trace.call(), size);
170 })
171 .expect("measurement succeeds");
172 assert_eq!(result.iterations, 80);
173 trace
174 }
175
176 #[test]
177 fn setup_runs_once_per_call_for_every_batch_size() {
178 for size in [
179 BatchSize::SmallInput,
180 BatchSize::LargeInput,
181 BatchSize::PerIteration,
182 BatchSize::NumBatches(4),
183 BatchSize::NumIterations(10),
184 ] {
185 let trace = trace_by_value(size);
186 assert_eq!(trace.setups.get(), 80, "{size:?}");
187 assert_eq!(trace.calls.get(), 80, "{size:?}");
188 }
189 }
190
191 #[test]
192 fn batch_count_follows_batch_size() {
193 let cases = [
195 (BatchSize::SmallInput, 2),
196 (BatchSize::LargeInput, 2 * 3),
197 (BatchSize::PerIteration, 80),
198 (BatchSize::NumBatches(4), 2 * 4),
199 (BatchSize::NumIterations(10), 2 * 4),
200 (BatchSize::NumIterations(15), 2 * 3),
201 ];
202 for (size, batches) in cases {
203 assert_eq!(trace_by_value(size).batches.get(), batches, "{size:?}");
204 }
205 }
206
207 #[test]
208 fn iter_batched_ref_uses_the_same_batches() {
209 let trace = Trace::default();
210 run_benchmark_loop(Bencher::with_config(fixed(2, 40)), |b| {
211 b.iter_batched_ref(|| trace.setup(), |_| trace.call(), BatchSize::LargeInput);
212 })
213 .expect("measurement succeeds");
214 assert_eq!(trace.setups.get(), 80);
215 assert_eq!(trace.batches.get(), 6);
216 }
217
218 #[test]
219 fn iter_batched_excludes_setup_time() {
220 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 5)), |b| {
221 b.iter_batched(
222 || {
223 std::thread::sleep(Duration::from_millis(1));
224 vec![1_u64, 2, 3]
225 },
226 |v| v.iter().sum::<u64>(),
227 BatchSize::SmallInput,
228 );
229 })
230 .expect("measurement succeeds");
231 assert!(
232 result.samples.iter().all(|s| s.duration_nanos < 500_000.0),
233 "samples: {:?}",
234 result.samples
235 );
236 }
237
238 struct SlowDrop;
240
241 impl Drop for SlowDrop {
242 fn drop(&mut self) {
243 std::thread::sleep(Duration::from_millis(1));
244 }
245 }
246
247 #[test]
248 fn iter_batched_drops_outputs_outside_the_timer() {
249 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 5)), |b| {
250 b.iter_batched(|| (), |()| SlowDrop, BatchSize::SmallInput);
251 })
252 .expect("measurement succeeds");
253 assert!(
254 result.samples.iter().all(|s| s.duration_nanos < 500_000.0),
255 "samples: {:?}",
256 result.samples
257 );
258 }
259
260 #[test]
261 fn iter_batched_ref_drops_inputs_and_outputs_outside_the_timer() {
262 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 5)), |b| {
263 b.iter_batched_ref(|| SlowDrop, |_| SlowDrop, BatchSize::PerIteration);
264 })
265 .expect("measurement succeeds");
266 assert!(
267 result.samples.iter().all(|s| s.duration_nanos < 500_000.0),
268 "samples: {:?}",
269 result.samples
270 );
271 }
272
273 struct Probe<'a> {
275 value: u64,
276 total: &'a Cell<u64>,
277 }
278
279 impl Drop for Probe<'_> {
280 fn drop(&mut self) {
281 self.total.set(self.total.get() + self.value);
282 }
283 }
284
285 #[test]
286 fn iter_batched_ref_mutation_is_visible() {
287 let total = Cell::new(0_u64);
288 let result = run_benchmark_loop(Bencher::with_config(fixed(3, 7)), |b| {
289 b.iter_batched_ref(
290 || Probe {
291 value: 0,
292 total: &total,
293 },
294 |probe| {
295 probe.value += 2;
296 probe.value
297 },
298 BatchSize::NumBatches(2),
299 );
300 })
301 .expect("measurement succeeds");
302 assert_eq!(result.iterations, 21);
303 assert_eq!(total.get(), 42);
305 }
306
307 #[test]
308 fn iter_batched_shared_setup_cadence() {
309 let setups = Cell::new(0_u64);
310 let calls = Cell::new(0_u64);
311 let result = run_benchmark_loop(Bencher::with_config(fixed(4, 10)), |b| {
312 b.iter_batched_shared(
313 3,
314 || setups.set(setups.get() + 1),
315 |_| calls.set(calls.get() + 1),
316 );
317 })
318 .expect("measurement succeeds");
319
320 assert_eq!(result.iterations, 40);
321 assert_eq!(calls.get(), 40);
322 assert_eq!(setups.get(), 4 * 10_u64.div_ceil(3));
324 }
325
326 #[test]
327 fn iter_batched_shared_zero_batch_size() {
328 let setups = Cell::new(0_u64);
329 let calls = Cell::new(0_u64);
330 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 5)), |b| {
331 b.iter_batched_shared(
332 0,
333 || setups.set(setups.get() + 1),
334 |_| calls.set(calls.get() + 1),
335 );
336 })
337 .expect("measurement succeeds");
338
339 assert_eq!(result.iterations, 10);
340 assert_eq!(calls.get(), 10);
341 assert_eq!(setups.get(), 10);
342 }
343
344 #[test]
345 fn iter_with_setup_excludes_setup_time() {
346 let setups = Cell::new(0_u64);
347 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 5)), |b| {
348 b.iter_with_setup(
349 || {
350 setups.set(setups.get() + 1);
351 std::thread::sleep(Duration::from_millis(1));
352 vec![1_u64, 2, 3]
353 },
354 |v| v.iter().sum::<u64>(),
355 );
356 })
357 .expect("measurement succeeds");
358
359 assert_eq!(setups.get(), 10);
360 assert_eq!(result.iterations, 10);
361 assert!(
363 result.samples.iter().all(|s| s.duration_nanos < 500_000.0),
364 "samples: {:?}",
365 result.samples
366 );
367 }
368
369 #[test]
370 fn slow_setup_keeps_every_sample() {
371 let config = MeasureConfig {
372 warmup_ns: 5_000_000,
373 measurement_ns: 50_000_000,
374 ..quick(10)
375 };
376 let result = run_benchmark_loop(Bencher::with_config(config), |b| {
377 b.iter_with_setup(|| std::thread::sleep(Duration::from_millis(1)), |()| 1_u64);
378 })
379 .expect("measurement succeeds");
380
381 assert_eq!(result.samples.len(), 10);
383 assert!(!result.truncated);
384 assert!(
385 result.iters_per_sample <= 10,
386 "ips: {}",
387 result.iters_per_sample
388 );
389 }
390
391 #[test]
392 fn iter_with_setup_chunks_large_batches() {
393 let trace = Trace::default();
394 let result = run_benchmark_loop(Bencher::with_config(fixed(2, 2_500)), |b| {
395 b.iter_with_setup(|| trace.setup(), |()| trace.call());
396 })
397 .expect("measurement succeeds");
398
399 assert_eq!(result.iterations, 5_000);
400 assert_eq!(trace.setups.get(), 5_000);
401 assert_eq!(trace.calls.get(), 5_000);
402 assert_eq!(trace.batches.get(), 6);
404 }
405}