Skip to main content

alux_bench/
measure.rs

1//! States the stating and the measuring of a benchmark, which are separate.
2
3use crate::{BenchCases, BenchGroup, BenchRounds, BenchRoutine, BenchSentRoutine, BenchStated};
4use alux_ext::ext;
5use core::time::Duration;
6
7/// Interprets the stating of a benchmark.
8///
9/// What a bench denotes is the interpretation's: one states the groups as a value to fold later,
10/// another states them straight into whatever its harness holds. Nothing is measured by stating
11/// it, so what an interpretation borrows, and when, is never the statement's business.
12pub trait BenchAlg {
13    /// Carries a stated bench.
14    type Bench;
15
16    /// States a bench of no groups.
17    fn nothing(&self) -> Self::Bench;
18
19    /// States one group of cases, named `group`.
20    fn group(&self, group: &'static str, cases: BenchCases<'static>) -> Self::Bench;
21
22    /// States the groups of `next` after the groups of `first`.
23    fn then(&self, first: Self::Bench, next: Self::Bench) -> Self::Bench;
24}
25
26/// Interprets the measuring of a stated bench.
27///
28/// Reading the bench is the interpretation's own loop, so a harness holding something mutable
29/// holds it here rather than while the bench is being stated.
30pub trait MeasureBenchAlg {
31    /// Carries the bench this measures.
32    type Bench;
33
34    /// Measures every case of `bench`, in the order the bench states them.
35    fn measure(&mut self, bench: Self::Bench);
36}
37
38/// Derives the ways a bench states a group.
39#[ext(name = BenchExt)]
40pub impl<This> This
41where
42    This: BenchAlg,
43{
44    /// States one group whose cases are measured one at a time.
45    ///
46    /// A routine stating `Send` is taken as well, since a case that may be measured beside others
47    /// may also be measured alone.
48    fn one_at_a_time<Stated, Routine>(&self, group: &'static str, cases: Stated) -> This::Bench
49    where
50        Stated: IntoIterator<Item = (&'static str, Routine)>,
51        Routine: FnMut(BenchRounds) -> Duration + 'static,
52    {
53        let cases = cases.into_iter().map(|(subject, routine)| (subject, Box::new(routine) as BenchRoutine<'static>));
54
55        self.group(group, BenchCases::OneAtATime(cases.collect()))
56    }
57
58    /// States one group whose cases are measured at the same time.
59    fn together<Stated, Routine>(&self, group: &'static str, cases: Stated) -> This::Bench
60    where
61        Stated: IntoIterator<Item = (&'static str, Routine)>,
62        Routine: FnMut(BenchRounds) -> Duration + Send + 'static,
63    {
64        let cases = cases.into_iter().map(|(subject, routine)| (subject, Box::new(routine) as BenchSentRoutine));
65
66        self.group(group, BenchCases::Together(cases.collect()))
67    }
68
69    /// States every group of `stated`, in the order stated.
70    fn benches<Stated>(&self, stated: Stated) -> This::Bench
71    where
72        Stated: IntoIterator<Item = This::Bench>,
73    {
74        stated.into_iter().fold(self.nothing(), |bench, next| self.then(bench, next))
75    }
76}
77
78/// States a bench as the value it is, which is what an interpretation folds.
79#[derive(Clone, Copy, Debug, Default)]
80pub struct StatingBench;
81
82impl BenchAlg for StatingBench {
83    type Bench = BenchStated<'static>;
84
85    fn nothing(&self) -> Self::Bench {
86        BenchStated::new()
87    }
88
89    fn group(&self, group: &'static str, cases: BenchCases<'static>) -> Self::Bench {
90        BenchStated::new().group(BenchGroup::new(group, cases))
91    }
92
93    fn then(&self, first: Self::Bench, next: Self::Bench) -> Self::Bench {
94        first.then(next)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::BenchRounds;
102    use core::time::Duration;
103
104    /// One case, measured by a routine that answers what it was asked for.
105    fn case(subject: &'static str) -> (&'static str, BenchRoutine<'static>) {
106        (subject, Box::new(|rounds: BenchRounds| Duration::from_millis(rounds.count())))
107    }
108
109    #[test]
110    fn states_groups_in_the_order_stated() {
111        let stating = StatingBench;
112
113        let bench = stating.benches([
114            stating.one_at_a_time("close", [case("hyper"), case("axum")]),
115            stating.together("end", [("poem", Box::new(|_| Duration::from_secs(1)) as BenchSentRoutine)]),
116        ]);
117
118        let groups = bench.groups().map(|group| (group.group(), group.stated().len())).collect::<Vec<_>>();
119        assert_eq!(groups, [("close", 2), ("end", 1)]);
120    }
121
122    #[test]
123    fn states_what_measuring_a_group_at_once_would_cost() {
124        let stating = StatingBench;
125
126        let bench = stating.one_at_a_time("close", [case("hyper")]);
127
128        let stated = bench.groups().next().expect("the group stated");
129        assert!(matches!(stated.stated(), BenchCases::OneAtATime(_)));
130        assert_eq!(stated.stated().subjects().collect::<Vec<_>>(), ["hyper"]);
131    }
132}