binggan/
bench_input_group.rs

1use std::mem;
2
3use crate::output_value::OutputValue;
4use crate::plugins::{EventListener, PluginManager};
5use crate::{
6    bench::NamedBench, bench_id::BenchId, bench_runner::BenchRunner, parse_args, BenchGroup, Config,
7};
8
9/// `InputGroup<Input, OutputValue>` is a collection of benchmarks that are run with the same inputs.
10///
11/// It is self-contained and can be run independently.
12///
13/// The ownership of the inputs is transferred to the `InputGroup`.
14/// If this is not possible, use [BenchRunner](crate::BenchRunner) instead.
15pub struct InputGroup<I: 'static = (), O = ()> {
16    inputs: Vec<OwnedNamedInput<I>>,
17    benches_per_input: Vec<Vec<NamedBench<'static, I, O>>>,
18    runner: BenchRunner,
19}
20
21impl Default for InputGroup<()> {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl InputGroup<(), ()> {
28    /// Create a new InputGroup with no inputs.
29    pub fn new() -> Self {
30        Self::new_with_inputs(vec![("", ())])
31    }
32}
33
34/// Bundles data with some name and its input_size_in_bytes.
35pub struct OwnedNamedInput<I> {
36    pub(crate) name: String,
37    pub(crate) data: I,
38    pub(crate) input_size_in_bytes: Option<usize>,
39}
40
41impl<I: 'static, O: OutputValue + 'static> InputGroup<I, O> {
42    /// The inputs are a vector of tuples, where the first element is the name of the input and the
43    /// second element is the input itself.
44    pub fn new_with_inputs<S: Into<String>>(inputs: Vec<(S, I)>) -> Self {
45        Self::new_with_inputs_and_options(inputs, parse_args())
46    }
47    /// The inputs are a vector of tuples, where the first element is the name of the input and the
48    /// second element is the input itself.
49    pub(crate) fn new_with_inputs_and_options<S: Into<String>>(
50        inputs: Vec<(S, I)>,
51        options: Config,
52    ) -> Self {
53        use yansi::Condition;
54        yansi::whenever(Condition::TTY_AND_COLOR);
55
56        let inputs: Vec<OwnedNamedInput<I>> = inputs
57            .into_iter()
58            .map(|(name, input)| OwnedNamedInput {
59                name: name.into(),
60                data: input,
61                input_size_in_bytes: None,
62            })
63            .collect();
64        let runner = BenchRunner::new_with_options(options);
65        let mut benches_per_input = Vec::new();
66        // Can't use resize because of clone
67        for _input in &inputs {
68            benches_per_input.push(Vec::new());
69        }
70
71        InputGroup {
72            inputs,
73            runner,
74            benches_per_input,
75        }
76    }
77
78    /// Enables throughput reporting.
79    /// The passed closure should return the size of the input in bytes.
80    pub fn throughput<F>(&mut self, f: F)
81    where
82        F: Fn(&I) -> usize + 'static,
83    {
84        for input in &mut self.inputs {
85            input.input_size_in_bytes = Some(f(&input.data));
86        }
87    }
88
89    /// Register a benchmark with the given name and function.
90    ///
91    /// The return value of the function will be reported as the `OutputValue`
92    pub fn register<F, S: Into<String>>(&mut self, name: S, fun: F)
93    where
94        F: Fn(&I) -> O + 'static + Clone,
95    {
96        let name = name.into();
97
98        let num_iter_for_group = self.config().get_num_iter_for_group();
99        for (ord, input) in self.inputs.iter().enumerate() {
100            let bench_id = BenchId::from_bench_name(name.clone())
101                .runner_name(self.runner.name.as_deref())
102                .group_name(Some(input.name.clone()));
103            let named_bench: NamedBench<'static, I, O> =
104                NamedBench::new(bench_id, Box::new(fun.clone()), num_iter_for_group);
105
106            self.benches_per_input[ord].push(named_bench);
107        }
108    }
109
110    /// Run the benchmarks and report the results.
111    pub fn run(&mut self) {
112        let mut benches_per_input = mem::take(&mut self.benches_per_input);
113        for (ord, benches) in benches_per_input.iter_mut().enumerate() {
114            let input = &self.inputs[ord];
115            let mut group = BenchGroup::new(&mut self.runner);
116            group.set_name(&input.name);
117            // reverse so we can use pop and keep the order
118            benches.reverse();
119            while let Some(bench) = benches.pop() {
120                // The input lives in the InputGroup, so we can transmute the lifetime to 'static
121                // (probably).
122                let extended_input = unsafe { transmute_lifetime(&input.data) };
123
124                if let Some(input_size) = input.input_size_in_bytes {
125                    group.set_input_size(input_size);
126                }
127                group.register_named_with_input(bench, extended_input);
128            }
129            group.run();
130        }
131    }
132
133    /// Set the name of the group.
134    /// The name is printed before the benchmarks are run.
135    /// It is also used to distinguish when writing the results to disk.
136    pub fn set_name<S: AsRef<str>>(&mut self, name: S) {
137        self.runner.set_name(name);
138    }
139
140    /// Configure the benchmarking options.
141    ///
142    /// See the [Config] struct for more information.
143    pub fn config(&mut self) -> &mut Config {
144        &mut self.runner.config
145    }
146
147    /// Returns the plugin manager, which can be used to add and remove plugins.
148    /// See [crate::plugins::PluginManager] for more information.
149    pub fn get_plugin_manager(&mut self) -> &mut PluginManager {
150        self.runner.get_plugin_manager()
151    }
152
153    /// Add a new plugin and returns the PluginManager for chaining.
154    pub fn add_plugin<L: EventListener + 'static>(&mut self, listener: L) -> &mut PluginManager {
155        self.get_plugin_manager().add_plugin(listener)
156    }
157}
158
159unsafe fn transmute_lifetime<I>(input: &I) -> &'static I {
160    mem::transmute(input)
161}