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}