benchplot/bench/
builder.rs

1/*
2Copyright 2024 Owain Davies
3SPDX-License-Identifier: Apache-2.0 OR MIT
4*/
5
6use crate::{Bench, BenchFnArg, BenchFnNamed};
7use std::sync::Arc;
8
9/// Error type for `BenchBuilder`.
10#[derive(Debug, PartialEq, thiserror::Error)]
11pub enum BenchBuilderError {
12    /// Indicates that the number of repetitions is set to zero.
13    #[error("Repetitions must be greater than 0.")]
14    ZeroRepetitions,
15
16    /// Indicates that the sizes vector is empty.
17    #[error("The sizes vector must not be empty.")]
18    NoSizes,
19
20    /// Indicates that the functions vector is empty.
21    #[error("The functions vector must not be empty.")]
22    NoFunctions,
23}
24
25/// Builder for creating a `Bench` instance.
26pub struct BenchBuilder<'a, T, R> {
27    functions: Vec<BenchFnNamed<'a, T, R>>,
28    argfunc: BenchFnArg<T>,
29    sizes: Vec<usize>,
30    repetitions: usize,
31    parallel: bool,
32    assert_equal: bool,
33}
34
35impl<'a, T, R> BenchBuilder<'a, T, R> {
36    /// Creates a new `BenchBuilder` with required parameters.
37    ///
38    /// Mandatory parameters are required upfront and optional parameters are
39    /// configured through method chaining.
40    ///
41    /// By default, `repetitions` is set to 1, `parallel` to false, and
42    /// `assert_equal` to false.
43    pub fn new(
44        functions: Vec<BenchFnNamed<'a, T, R>>,
45        argfunc: BenchFnArg<T>,
46        sizes: Vec<usize>,
47    ) -> Self {
48        Self {
49            functions,
50            argfunc,
51            sizes,
52            repetitions: 1,
53            parallel: false,
54            assert_equal: false,
55        }
56    }
57
58    /// Sets the number of times to time each (input size, function) pair.
59    ///
60    /// For each (input size, function) pair, the function is timed
61    /// `repetitions` times and the average over the repetitions is used as the
62    /// benchmark value.
63    ///
64    /// **Default**: `1`.
65    pub fn repetitions(mut self, repetitions: usize) -> Self {
66        self.repetitions = repetitions;
67        self
68    }
69
70    /// Sets whether to run (input size, function) pair benchmarks in parallel.
71    ///
72    /// **Default**: `false`.
73    pub fn parallel(mut self, parallel: bool) -> Self {
74        self.parallel = parallel;
75        self
76    }
77
78    /// Sets whether to assert that all function return values are equal.
79    ///
80    /// When set to `true`, if there exists an input size such that the function
81    /// return values are not equal, then the program panics.
82    ///
83    /// If `repetitions` is greater than 1, then for each input size, only the
84    /// function return values from the last repetition are compared.
85    ///
86    /// **Default**: `false`.
87    pub fn assert_equal(mut self, assert_equal: bool) -> Self {
88        self.assert_equal = assert_equal;
89        self
90    }
91
92    /// Validates the configuration and builds a `Bench` instance.
93    pub fn build(self) -> Result<Bench<'a, T, R>, BenchBuilderError> {
94        if self.repetitions == 0 {
95            return Err(BenchBuilderError::ZeroRepetitions);
96        }
97        if self.sizes.is_empty() {
98            return Err(BenchBuilderError::NoSizes);
99        }
100        if self.functions.is_empty() {
101            return Err(BenchBuilderError::NoFunctions);
102        }
103        Ok(Bench {
104            functions: self
105                .functions
106                .into_iter()
107                .map(|(func, name)| (Arc::new(func), name))
108                .collect(),
109            argfunc: Arc::new(self.argfunc),
110            sizes: self.sizes,
111            repetitions: self.repetitions,
112            parallel: self.parallel,
113            assert_equal: self.assert_equal,
114            data: Vec::new(),
115        })
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    fn dummy_bench_fn(_: usize) -> usize {
124        0
125    }
126
127    fn dummy_arg_fn(size: usize) -> usize {
128        size
129    }
130
131    fn create_mandatory_args() -> (
132        Vec<BenchFnNamed<'static, usize, usize>>,
133        BenchFnArg<usize>,
134        Vec<usize>,
135    ) {
136        let functions: Vec<BenchFnNamed<'static, usize, usize>> =
137            vec![(Box::new(dummy_bench_fn), "Dummy Function")];
138        let argfunc: BenchFnArg<usize> = Box::new(dummy_arg_fn);
139        let sizes = vec![10, 20, 30];
140
141        (functions, argfunc, sizes)
142    }
143
144    #[test]
145    fn test_bench_builder_only_mandatory_args() {
146        let (functions, argfunc, sizes) = create_mandatory_args();
147
148        let builder = BenchBuilder::new(functions, argfunc, sizes);
149        let result = builder.build();
150
151        assert!(result.is_ok());
152    }
153
154    #[test]
155    fn test_setting_repetitions() {
156        let (functions, argfunc, sizes) = create_mandatory_args();
157
158        let builder =
159            BenchBuilder::new(functions, argfunc, sizes).repetitions(8);
160        let bench = builder.build().unwrap();
161
162        assert_eq!(bench.repetitions, 8);
163    }
164
165    #[test]
166    fn test_setting_parallel() {
167        let (functions, argfunc, sizes) = create_mandatory_args();
168
169        let builder =
170            BenchBuilder::new(functions, argfunc, sizes).parallel(true);
171        let bench = builder.build().unwrap();
172
173        assert!(bench.parallel);
174    }
175
176    #[test]
177    fn test_assert_equal() {
178        let (functions, argfunc, sizes) = create_mandatory_args();
179
180        let builder =
181            BenchBuilder::new(functions, argfunc, sizes).assert_equal(true);
182        let bench = builder.build().unwrap();
183
184        assert!(bench.assert_equal);
185    }
186
187    #[test]
188    fn test_zero_repetitions() {
189        let (functions, argfunc, sizes) = create_mandatory_args();
190
191        let builder =
192            BenchBuilder::new(functions, argfunc, sizes).repetitions(0);
193        let result = builder.build();
194
195        assert!(matches!(result, Err(BenchBuilderError::ZeroRepetitions)));
196    }
197
198    #[test]
199    fn test_no_sizes() {
200        let functions: Vec<BenchFnNamed<'static, usize, usize>> =
201            vec![(Box::new(dummy_bench_fn), "Dummy Function")];
202        let argfunc: BenchFnArg<usize> = Box::new(dummy_arg_fn);
203
204        let builder = BenchBuilder::new(functions, argfunc, Vec::new());
205        let result = builder.build();
206
207        assert!(matches!(result, Err(BenchBuilderError::NoSizes)));
208    }
209
210    #[test]
211    fn test_no_functions() {
212        let functions: Vec<BenchFnNamed<'static, usize, usize>> = Vec::new();
213        let argfunc: BenchFnArg<usize> = Box::new(dummy_arg_fn);
214        let sizes = vec![10, 20, 30];
215
216        let builder = BenchBuilder::new(functions, argfunc, sizes);
217        let result = builder.build();
218
219        assert!(matches!(result, Err(BenchBuilderError::NoFunctions)));
220    }
221}