criterion_polyglot/
lib.rs

1#![warn(missing_docs)]
2
3// Copyright (c) 2022 Ross Williams
4
5//! `criterion-polyglot` provides an extension trait for [`criterion::Criterion`] and
6//! [`criterion::BenchmarkGroup`] that provides benchmark methods for non-Rust programming
7//! languages.
8//!
9//! `criterion-polyglot` currently supports the following languages:
10//! * [Python 3](CriterionPolyglotExt::python_benchmark)
11//! * [Ruby](CriterionPolyglotExt::ruby_benchmark)
12//! * [Go](CriterionPolyglotExt::go_benchmark)
13//! * [Zig](CriterionPolyglotExt::zig_benchmark)
14//! * [C](CriterionPolyglotExt::c_benchmark)
15//!
16//! See [`CriterionPolyglotExt`] for detailed documentation on constructing benchmarks.
17//!
18//! # Examples
19//!
20//! #### Rust `Vec` versus Python `list`
21//! ```
22//! use criterion::{black_box, criterion_group, criterion_main, Criterion};
23//! use criterion_polyglot::{BenchSpec, CriterionPolyglotExt};
24//!
25//! fn bench(c: &mut Criterion) {
26//!     const LENGTH: u64 = 65_536;
27//!     let mut source = Vec::with_capacity(LENGTH as usize);
28//!     source.extend(0..LENGTH);
29//!
30//!     c.bench_function("Vec", |b| b.iter(||
31//!         Vec::new().extend(black_box(source.iter().copied()))
32//!     ));
33//!
34//!     c.python_benchmark("list",
35//!         BenchSpec::new(r#"
36//!             l = list()
37//!             l += source
38//!         "#).with_global_init(r#"
39//!             source = [n for n in range(0, 65536)]
40//!         "#)
41//!     );
42//! }
43//!
44//! # // The following two macros are cosmetic only and generate
45//! # // functions that are not invoked by the doctest
46//! criterion_group!(benches, bench);
47//! criterion_main!(benches);
48//! #
49//! # // This helper function actually runs the benchmark
50//! # criterion_polyglot::test_helpers::run_short_bench(bench);
51//! ```
52//!
53//! #### Rust vs. Zig Hypothetical Tree Structure
54//! ```
55//! # mod some_tree {
56//! #     use std::marker::PhantomData;
57//! #     pub struct SomeTree<T>(PhantomData<T>);
58//! #     impl<T> SomeTree<T> {
59//! #         pub fn new() -> Self {
60//! #             Self(PhantomData)
61//! #         }
62//! #         pub fn insert(&mut self, _: T) {}
63//! #     }
64//! # }
65//! # #[derive(Clone)]
66//! # struct Element;
67//! use criterion::{black_box, BatchSize, Criterion};
68//! use criterion_polyglot::{BenchSpec, CriterionPolyglotExt};
69//! use some_tree::SomeTree;
70//! 
71//! fn make_elements() -> Vec<Element> {
72//!     /* */
73//!     # Vec::new()
74//! }
75//! 
76//! fn bench(c: &mut Criterion) {
77//!     let mut g = c.benchmark_group("some_tree");
78//! 
79//!     g.bench_function("Rust", |b| {
80//!         let mut tree = SomeTree::new();
81//!         let elements = make_elements();
82//!         b.iter_batched(
83//!             || elements.clone(),
84//!             |elements| for element in elements { tree.insert(black_box(element)); },
85//!             BatchSize::SmallInput,
86//!         );
87//!     });
88//!     g.zig_benchmark("Zig",
89//!         BenchSpec::new(r#"
90//! #           // Hidden lines obscure nonsense code from visible documentation
91//! #           // that allows this doctest to compile and run
92//!             for (elements) |element| {
93//!                 tree.insert(element);
94//!             }
95//!         "#).with_sample_init(r#"
96//!             const elements = make_elements(allocator);
97//!             var tree = new_tree(allocator);
98//!         "#).with_global_init(r#"
99//!             var arena = ArenaAllocator.init(std.heap.page_allocator);
100//!             defer arena.deinit();
101//!             const allocator = arena.allocator();
102//!         "#)
103//!         .with_imports(r#"
104//!             const std = @import("std");
105//! #           const ArrayList = std.ArrayList;
106//!             const Allocator = std.mem.Allocator;
107//!             const ArenaAllocator = std.heap.ArenaAllocator;
108//!         "#)
109//!         .with_declarations(r#"
110//!             const SomeTree = struct {
111//!                 pub fn insert(self: *SomeTree, value: u8) void {
112//!                     // ... tree implementation ...
113//! #                   _ = self;
114//! #                   _ = value;
115//!                 }
116//!                 // ...
117//!             };
118//!             fn make_elements(allocator: Allocator) []u8 {
119//!                 // ...
120//! #               var list = ArrayList(u8).init(allocator);
121//! #               return list.toOwnedSlice();
122//!             }
123//!             fn new_tree(allocator: Allocator) SomeTree {
124//!                 // ...
125//! #               _ = allocator;
126//! #               return SomeTree { };
127//!             }
128//!         "#)
129//!     );
130//! 
131//! }
132//! # criterion_polyglot::test_helpers::run_short_bench(bench);
133//! ```
134//!
135mod helper;
136mod lang;
137mod runner;
138mod spec;
139
140#[cfg(feature = "c")]
141#[doc(no_inline)]
142pub use cc;
143use criterion::{measurement::WallTime, Criterion, BenchmarkGroup};
144
145pub use spec::BenchSpec;
146
147// This should be possible to do with #[cfg(doctest)],
148// but it's broken: https://github.com/rust-lang/rust/issues/67295
149#[doc(hidden)]
150#[path = "../tests/helpers.rs"]
151pub mod test_helpers;
152
153#[doc = include_str!("../README.md")]
154#[cfg(doctest)]
155pub struct ReadmeDoctests;
156
157/// `CriterionPolyglotExt` is an extension trait for [`criterion::Criterion`] and
158/// [`criterion::BenchmarkGroup`] that provides benchmark methods for various non-Rust programming
159/// languages.
160///
161/// ### Function Parameters
162///
163/// * `id: &str` - identifies the benchmark in Criterion's output
164/// * `spec: `[`BenchSpec`] - defines the benchmark code, including non-benchmarked initialization
165///                           code; see the [`BenchSpec`] documentation for detailed usage
166pub trait CriterionPolyglotExt {
167    /// Benchmark Python 3 code
168    ///
169    /// See [trait-level documentation](Self#function-parameters) for argument descriptions.
170    fn python_benchmark(&mut self, id: &str, spec: BenchSpec) -> &mut Self;
171
172    /// Benchmark Ruby code
173    ///
174    /// See [trait-level documentation](Self#function-parameters) for argument descriptions.
175    fn ruby_benchmark(&mut self, id: &str, spec: BenchSpec) -> &mut Self;
176
177    /// Benchmark Go code
178    ///
179    /// See [trait-level documentation](Self#function-parameters) for argument descriptions.
180    fn go_benchmark(&mut self, id: &str, spec: BenchSpec) -> &mut Self;
181
182    /// Benchmark Zig code
183    ///
184    /// See [trait-level documentation](Self#function-parameters) for argument descriptions.
185    fn zig_benchmark(&mut self, id: &str, spec: BenchSpec) -> &mut Self;
186
187    #[cfg(feature = "c")]
188    /// Benchmark C code, using a custom compiler
189    ///
190    /// See [trait-level documentation](Self#function-parameters) for details on the `id` and `spec`
191    /// arguments.
192    ///
193    /// The `builder` argument takes an instance of [`cc::Build`] that can be
194    /// constructed to customize which C compiler is used to build the benchmark code and with
195    /// which options. To ensure you construct an instance of `cc::Build` that is compatible with
196    /// the version `criterion-polyglot` depends on, you may want to use the `cc` module
197    /// re-exported as [`criterion_polyglot::cc`](crate#reexport.cc).
198    fn c_benchmark_with_builder(
199        &mut self,
200        id: &str,
201        spec: BenchSpec,
202        builder: cc::Build,
203    ) -> &mut Self;
204
205    #[cfg(feature = "c")]
206    /// Benchmark C code
207    ///
208    /// See [trait-level documentation](Self#function-parameters) for argument descriptions.
209    ///
210    /// Uses the C compiler as configured by [`cc::Build::default()`], then set to the same
211    /// optimization level as `rustc` (determined by the `OPT_LEVEL` environment variable)
212    ///
213    /// Header files already included by the benchmarking harness are:
214    /// ```c
215    /// #include <stdio.h>
216    /// #include <stdlib.h>
217    /// #include <string.h>
218    /// #include <limits.h>
219    /// #include <sys/errno.h>
220    /// #include <time.h>
221    /// #include <sys/time.h>
222    /// ```
223    /// and on macOS:
224    /// ```c
225    /// #include <mach/mach.h>
226    /// #include <mach/clock.h>
227    /// ```
228    fn c_benchmark(
229        &mut self,
230        id: &str,
231        spec: BenchSpec,
232    ) -> &mut Self {
233        self.c_benchmark_with_builder(id, spec, cc::Build::default())
234    }
235}
236
237macro_rules! spawn_bench {
238    ($lang:ident, $this:ident, $id:expr, $target:expr) => {
239        let mut bench = lang::$lang::spawn($target);
240        $this.bench_function($id, |b| b.iter_custom(|n| bench.run(n)));
241        bench.terminate();
242    };
243    ($lang:ident, $this:ident, $id:expr, $target:expr, $($extra:expr),+) => {
244        let mut bench = lang::$lang::spawn($target, $($extra),+);
245        $this.bench_function($id, |b| b.iter_custom(|n| bench.run(n)));
246        bench.terminate();
247    };
248}
249
250macro_rules! impl_interpreted_lang {
251    ($lang:ident) => {
252    ::paste::paste! {
253        fn [<$lang _benchmark>](&mut self, id: &str, spec: BenchSpec) -> &mut Self {
254            spawn_bench!($lang, self, id, spec);
255            self
256        }
257    }
258    };
259}
260
261macro_rules! impl_compiled_lang {
262    ($lang:ident) => {
263    ::paste::paste! {
264        fn [<$lang _benchmark>](&mut self, id: &str, spec: BenchSpec) -> &mut Self {
265            let bench_executable = lang::$lang::compile(spec);
266
267            spawn_bench!($lang, self, id, &bench_executable);
268            self
269        }
270    }
271    };
272}
273
274macro_rules! impl_compiled_lang_with_params {
275    ($lang:ident, $fn_name:ident, compile = $($param_ident:ident: $param_ty:ty),+) => {
276    ::paste::paste! {
277        fn [<$lang _benchmark_ $fn_name>](&mut self, id: &str, spec: BenchSpec, $($param_ident: $param_ty),+) -> &mut Self {
278            let bench_executable = lang::$lang::compile(spec, $($param_ident),+);
279
280            spawn_bench!($lang, self, id, &bench_executable);
281            self
282        }
283    }
284    };
285    ($lang:ident, $fn_name:ident, spawn = $($param_ident:ident: $param_ty:ty),+) => {
286    ::paste::paste! {
287        fn [<$lang _benchmark_ $fn_name>](&mut self, id: &str, spec: BenchSpec, $($param_ident: $param_ty),+) -> &mut Self {
288            let bench_executable = lang::$lang::compile(spec);
289
290            spawn_bench!($lang, self, id, &bench_executable, $($param_ident),+);
291            self
292        }
293    }
294    };
295}
296
297macro_rules! impl_for {
298    ($($for:ty)|+ => $impls:tt) => {
299$(
300        impl CriterionPolyglotExt for $for
301            $impls
302    )+
303    };
304}
305
306
307impl_for! {
308    Criterion<WallTime> | BenchmarkGroup<'_, WallTime> => {
309        impl_interpreted_lang!(python);
310        impl_interpreted_lang!(ruby);
311        impl_compiled_lang!(zig);
312        impl_compiled_lang!(go);
313
314        #[cfg(feature = "c")]
315        impl_compiled_lang_with_params!(c, with_builder, compile = builder: cc::Build);
316    }
317}