Skip to main content

fluxbench_core/bencher/
custom.rs

1//! `iter_custom`: the body times its own calls.
2
3use super::engine::SampleRaw;
4use super::state::Bencher;
5use std::time::Duration;
6
7impl Bencher {
8    /// Measure a routine that times itself.
9    ///
10    /// `routine(iters)` runs `iters` calls and returns the time they took.
11    /// The engine runs warmup, sizes the samples, and calls it once per batch.
12    /// The returned time is the sample total. No engine timer overhead is removed from it.
13    /// Allocation counters cover the whole `routine` call. Cycle counts are 0.
14    #[inline]
15    pub fn iter_custom<F>(&mut self, mut routine: F)
16    where
17        F: FnMut(u64) -> Duration,
18    {
19        if !self.begin_iter() {
20            return;
21        }
22        self.run_loop(|b, n| {
23            b.alloc_reset();
24            let elapsed = routine(n);
25            let (alloc_b, alloc_c) = b.alloc_read();
26            SampleRaw {
27                ns: u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX),
28                alloc_b,
29                alloc_c,
30                ..SampleRaw::default()
31            }
32        });
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use crate::bencher::test_support::{fixed, quick};
39    use crate::bencher::{Bencher, run_benchmark_loop};
40    use std::cell::Cell;
41    use std::time::Duration;
42
43    #[test]
44    fn reported_time_becomes_the_sample_mean() {
45        let result = run_benchmark_loop(Bencher::with_config(fixed(4, 25)), |b| {
46            b.iter_custom(|iters| Duration::from_nanos(iters * 100));
47        })
48        .expect("measurement succeeds");
49        assert_eq!(result.samples.len(), 4);
50        assert!(result.samples.iter().all(|s| s.duration_nanos == 100.0));
51        assert!(result.samples.iter().all(|s| s.iterations == 25));
52        assert_eq!(result.iterations, 100);
53        assert_eq!(result.total_time_ns, 100 * 100);
54    }
55
56    #[test]
57    fn every_requested_iteration_is_accounted() {
58        let requested = Cell::new(0_u64);
59        let result = run_benchmark_loop(Bencher::with_config(quick(10)), |b| {
60            b.iter_custom(|iters| {
61                requested.set(requested.get() + iters);
62                Duration::from_nanos(iters * 10)
63            });
64        })
65        .expect("measurement succeeds");
66        assert_eq!(result.samples.len(), 10);
67        assert!(result.warmup_iterations > 0);
68        assert_eq!(
69            requested.get(),
70            result.iterations + result.warmup_iterations
71        );
72        assert_eq!(result.total_time_ns, result.iterations * 10);
73    }
74
75    #[test]
76    fn warmup_sizes_samples_from_the_wall_clock() {
77        // The routine reports 1 ns per call but sleeps 1 ms per call.
78        let result = run_benchmark_loop(Bencher::with_config(quick(5)), |b| {
79            b.iter_custom(|iters| {
80                std::thread::sleep(Duration::from_millis(iters));
81                Duration::from_nanos(iters)
82            });
83        })
84        .expect("measurement succeeds");
85        // 2 ms per 10 ms / 5 samples: about 2 calls per sample, not millions.
86        assert!(
87            result.iters_per_sample <= 10,
88            "ips: {}",
89            result.iters_per_sample
90        );
91    }
92}