aboutsummaryrefslogtreecommitdiff
path: root/primrose/crates/candelabra-benchmarker/src/bench.rs
blob: 9c4cf7403262066c8cc4fcc1480b77c2456ee6eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use std::{
    cmp,
    hint::black_box,
    time::{Duration, Instant},
};

use crate::BenchmarkResult;

/// Benchmark an operation for approx 5 seconds, returning the results.
///
/// `setup` is used to create the thing `op` acts on, and `undo` is called between each run to undo `op`.
/// If `undo` is invalid, this will return garbage results.
///
/// Warm-up for the setup is done beforehand.
pub fn benchmark_op<T>(
    mut setup: impl FnMut() -> T,
    mut op: impl FnMut(&mut T),
    mut undo: impl FnMut(&mut T),
) -> BenchmarkResult {
    // let loop_end = Instant::now() + Duration::from_secs(5);
    let loop_end = Instant::now() + Duration::from_millis(100);

    let mut times = 0;
    let mut min = Duration::from_secs(u64::MAX);
    let mut max = Duration::from_secs(0);
    let mut sum = Duration::from_secs(0);

    let mut target = setup();
    while Instant::now() + max < loop_end {
        #[allow(clippy::unit_arg)] // pretty sure this is necessary to prevent optimisations
        let duration = time_singular(|| black_box(op(&mut target)));
        undo(&mut target);

        min = cmp::min(min, duration);
        max = cmp::max(max, duration);
        sum += duration;
        times += 1;
    }

    BenchmarkResult {
        times,
        min,
        max,
        avg: sum / times as u32,
    }
}

fn time_singular(f: impl FnOnce()) -> Duration {
    let start = Instant::now();
    f();
    let end = Instant::now();
    end - start
}

#[cfg(test)]
mod tests {
    use super::benchmark_op;
    use std::time::Duration;

    #[test]
    fn benchmark_op_resets_properly() {
        benchmark_op(
            || false,
            |b| {
                assert!(!(*b));
                *b = true;
            },
            |b| {
                *b = false;
            },
        );
    }

    #[test]
    fn benchmark_op_times_properly() {
        let results = benchmark_op(
            || (),
            |_| std::thread::sleep(Duration::from_millis(5)),
            |_| {},
        );

        let avg_millis = results.avg.as_nanos() as f32 / (10.0_f32).powi(6);
        dbg!(avg_millis);

        assert!(
            (avg_millis - 5.0).abs() < 0.1,
            "sleeping for 5ms takes roughly 5ms"
        )
    }
}