Skip to content

Commit aa79cf8

Browse files
committed
Add benchlib
Library for defining and measuring (micro)benchmarks.
1 parent 572f22e commit aa79cf8

File tree

11 files changed

+360
-35
lines changed

11 files changed

+360
-35
lines changed

Cargo.lock

Lines changed: 76 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["collector", "site", "database", "intern"]
2+
members = ["collector", "collector/benchlib", "site", "database", "intern"]
33
exclude = ["collector/benchmarks", "rust/src"]
44

55
[profile.release.package.site]

collector/benchlib/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "benchlib"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Library for defining and measuring benchmarks of Rust code"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
anyhow = "1.0.61"
11+
serde = { version = "1.0.143", features = ["derive"] }
12+
serde_json = "1.0.83"
13+
log = "0.4.17"
14+
env_logger = "0.9.0"
15+
clap = { version = "3.2", features = ["derive"] }
16+
17+
[target.'cfg(unix)'.dependencies]
18+
perf-event = "0.4.7"

collector/benchlib/src/benchmark.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::cli::{parse_cli, Args, BenchmarkArgs};
2+
use crate::measure::benchmark_function;
3+
use crate::messages::BenchmarkResult;
4+
use log::LevelFilter;
5+
use std::collections::HashMap;
6+
7+
/// Create a new benchmark suite. Use the closure argument to define benchmarks.
8+
pub fn benchmark_suite<F: FnOnce(&mut BenchmarkSuite)>(define_func: F) {
9+
env_logger::Builder::from_default_env()
10+
.filter_level(LevelFilter::Info)
11+
.init();
12+
let mut suite = BenchmarkSuite::new();
13+
define_func(&mut suite);
14+
suite.run().expect("Benchmark suite has failed");
15+
}
16+
17+
#[derive(Default)]
18+
pub struct BenchmarkSuite {
19+
benchmarks: HashMap<&'static str, BenchmarkWrapper>,
20+
}
21+
22+
impl BenchmarkSuite {
23+
pub fn new() -> Self {
24+
Self::default()
25+
}
26+
27+
/// Registers a single benchmark.
28+
/// `func` should return a closure that will be benchmarked.
29+
pub fn register<F: Fn() -> Bench + 'static, R, Bench: FnOnce() -> R + 'static>(
30+
&mut self,
31+
name: &'static str,
32+
func: F,
33+
) {
34+
// We want to monomorphize the target `func` and then wrap it in a Box, to avoid going
35+
// through a vtable when we execute the benchmarked function.
36+
let benchmark_func = Box::new(move || {
37+
let bench_fn = func();
38+
benchmark_function(name, bench_fn)
39+
});
40+
let benchmark_def = BenchmarkWrapper {
41+
func: benchmark_func,
42+
};
43+
if self.benchmarks.insert(name, benchmark_def).is_some() {
44+
panic!("Benchmark {} was registered twice", name);
45+
}
46+
}
47+
48+
/// Execute the benchmark suite. It will parse CLI arguments and decide what to do based on
49+
/// them.
50+
pub fn run(self) -> anyhow::Result<()> {
51+
let args = parse_cli()?;
52+
match args {
53+
Args::Benchmark(args) => {
54+
self.run_benchmark(args)?;
55+
}
56+
}
57+
58+
Ok(())
59+
}
60+
61+
fn run_benchmark(self, args: BenchmarkArgs) -> anyhow::Result<()> {
62+
let mut items: Vec<_> = self.benchmarks.into_iter().collect();
63+
items.sort_unstable_by_key(|item| item.0);
64+
65+
let mut results: Vec<BenchmarkResult> = Vec::with_capacity(items.len());
66+
for (name, def) in items {
67+
for i in 0..args.iterations {
68+
let result = (def.func)()?;
69+
log::info!("Benchmark (run {i}) `{}` completed: {:?}", name, result);
70+
results.push(result);
71+
}
72+
}
73+
74+
println!("{}", serde_json::to_string(&results)?);
75+
Ok(())
76+
}
77+
}
78+
79+
struct BenchmarkWrapper {
80+
func: Box<dyn Fn() -> anyhow::Result<BenchmarkResult>>,
81+
}
82+
83+
/// Copied from `iai`, so that we don't have to use unstable features.
84+
pub fn black_box<T>(dummy: T) -> T {
85+
unsafe {
86+
let ret = std::ptr::read_volatile(&dummy);
87+
std::mem::forget(dummy);
88+
ret
89+
}
90+
}

0 commit comments

Comments
 (0)