Skip to main content

fluxbench_core/bencher/
iter.rs

1//! `iter`: measure a closure with no per-call setup.
2
3use super::engine::SampleRaw;
4use super::state::Bencher;
5use crate::measure::Timer;
6use std::hint::black_box;
7
8impl Bencher {
9    /// Measure `f`. This call runs warmup and every sample.
10    ///
11    /// One timer pair covers a whole batch of calls.
12    /// Each sample holds the per-iteration mean of that batch.
13    #[inline]
14    pub fn iter<T, F>(&mut self, mut f: F)
15    where
16        F: FnMut() -> T,
17    {
18        if !self.begin_iter() {
19            return;
20        }
21        self.run_loop(|b, n| {
22            let mut raw = SampleRaw::default();
23            b.alloc_reset();
24            let timer = Timer::start();
25            for _ in 0..n {
26                black_box(f());
27            }
28            let (ns, cycles) = timer.stop();
29            raw.add(ns, cycles, b.alloc_read());
30            raw
31        });
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use crate::bencher::test_support::{fixed, quick};
38    use crate::bencher::{Bencher, MeasureConfig, MeasureError, run_benchmark_loop};
39    use std::cell::Cell;
40    use std::sync::atomic::{AtomicUsize, Ordering};
41
42    #[test]
43    fn runner_called_once() {
44        let calls = AtomicUsize::new(0);
45        let result = run_benchmark_loop(Bencher::with_config(quick(10)), |b| {
46            calls.fetch_add(1, Ordering::Relaxed);
47            b.iter(|| 1_u64);
48        });
49        assert!(result.is_ok());
50        assert_eq!(calls.load(Ordering::Relaxed), 1);
51    }
52
53    #[test]
54    fn iter_calls_f_many_times_per_sample() {
55        let count = Cell::new(0_u64);
56        let result = run_benchmark_loop(Bencher::with_config(quick(10)), |b| {
57            b.iter(|| count.set(count.get() + 1));
58        })
59        .expect("measurement succeeds");
60
61        assert_eq!(result.samples.len(), 10);
62        assert!(result.iters_per_sample > 1);
63        assert!(count.get() > 100 * result.samples.len() as u64);
64        assert!(
65            result
66                .samples
67                .iter()
68                .all(|s| s.iterations == result.iters_per_sample)
69        );
70        assert_eq!(count.get(), result.iterations + result.warmup_iterations);
71    }
72
73    #[test]
74    fn sample_count_honoured_above_100() {
75        let result = run_benchmark_loop(Bencher::with_config(quick(250)), |b| b.iter(|| 1_u64))
76            .expect("measurement succeeds");
77        assert_eq!(result.samples.len(), 250);
78        assert!(!result.truncated);
79    }
80
81    #[test]
82    fn fixed_mode_samples_n() {
83        let count = Cell::new(0_u64);
84        let result = run_benchmark_loop(Bencher::with_config(fixed(10, 3)), |b| {
85            b.iter(|| count.set(count.get() + 1));
86        })
87        .expect("measurement succeeds");
88
89        assert_eq!(result.samples.len(), 10);
90        assert!(result.samples.iter().all(|s| s.iterations == 3));
91        assert_eq!(result.iterations, 30);
92        assert_eq!(result.warmup_iterations, 0);
93        assert_eq!(count.get(), 30);
94    }
95
96    #[test]
97    fn fixed_mode_500_samples_of_one_iteration() {
98        let result = run_benchmark_loop(Bencher::with_config(fixed(500, 1)), |b| b.iter(|| 3_u64))
99            .expect("measurement succeeds");
100        assert_eq!(result.samples.len(), 500);
101        assert_eq!(result.iterations, 500);
102    }
103
104    #[test]
105    fn subnanosecond_resolution() {
106        let result = run_benchmark_loop(Bencher::with_config(fixed(20, 10_000)), |b| {
107            b.iter(|| 1_u64);
108        })
109        .expect("measurement succeeds");
110
111        // A trivial call costs under 1 ns or a fraction of one.
112        // Integer division returns whole numbers only.
113        assert!(
114            result
115                .samples
116                .iter()
117                .any(|s| s.duration_nanos < 1.0 || s.duration_nanos.fract() != 0.0),
118            "samples: {:?}",
119            result.samples
120        );
121    }
122
123    #[test]
124    fn warmup_uses_constant_memory() {
125        let config = MeasureConfig {
126            warmup_ns: 20_000_000,
127            ..quick(10)
128        };
129        let mut bencher = Bencher::with_config(config);
130        bencher.iter(|| 1_u64);
131
132        // Warmup ran many calls, yet the only buffer holds `sample_count` samples.
133        assert!(bencher.warmup_iterations > 10_000);
134        assert_eq!(bencher.samples.len(), 10);
135        assert!(bencher.samples.capacity() <= 2 * 10);
136    }
137
138    #[test]
139    fn run_loop_respects_min_iterations() {
140        let config = MeasureConfig {
141            min_iterations: Some(100),
142            max_iterations: Some(100),
143            measurement_ns: 1,
144            ..quick(10)
145        };
146        let result = run_benchmark_loop(Bencher::with_config(config), |b| b.iter(|| 42_u64))
147            .expect("measurement succeeds");
148        assert_eq!(result.iterations, 100);
149    }
150
151    #[test]
152    fn run_loop_raises_max_to_min() {
153        let config = MeasureConfig {
154            min_iterations: Some(200),
155            max_iterations: Some(50),
156            measurement_ns: 1,
157            ..quick(10)
158        };
159        let result = run_benchmark_loop(Bencher::with_config(config), |b| b.iter(|| 7_u64))
160            .expect("measurement succeeds");
161        assert_eq!(result.iterations, 200);
162    }
163
164    #[test]
165    fn second_iter_call_returns_error() {
166        let second = Cell::new(0_u64);
167        let result = run_benchmark_loop(Bencher::with_config(fixed(2, 1)), |b| {
168            b.iter(|| 1_u64);
169            b.iter(|| second.set(second.get() + 1));
170        });
171        let err = result.err();
172        assert_eq!(err, Some(MeasureError::MultipleIterCalls));
173        assert_eq!(
174            err.map(|e| e.to_string()).as_deref(),
175            Some("benchmark body called more than one b.iter* method")
176        );
177        assert_eq!(second.get(), 0);
178    }
179
180    #[test]
181    fn no_iter_call_returns_error() {
182        let result = run_benchmark_loop(Bencher::with_config(fixed(2, 1)), |_| {});
183        let err = result.err();
184        assert_eq!(err, Some(MeasureError::NoIterCall));
185        assert_eq!(
186            err.map(|e| e.to_string()).as_deref(),
187            Some("benchmark body never called b.iter*")
188        );
189    }
190}