Skip to content

Commit ca9ae60

Browse files
authored
Merge pull request #1644 from Kobzol/runtime-benchmark-harness
Make runtime benchmark harness more flexible to use
2 parents b8e388e + 8b2865d commit ca9ae60

File tree

7 files changed

+84
-85
lines changed

7 files changed

+84
-85
lines changed

collector/benchlib/src/benchmark.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,26 @@ use std::collections::HashMap;
77

88
/// Create and run a new benchmark group. Use the closure argument to register
99
/// the individual benchmarks.
10-
pub fn run_benchmark_group<F>(register: F)
10+
pub fn run_benchmark_group<'a, F>(register: F)
1111
where
12-
F: FnOnce(&mut BenchmarkGroup),
12+
F: FnOnce(&mut BenchmarkGroup<'a>),
1313
{
1414
env_logger::init();
1515

16-
let mut group = BenchmarkGroup::new();
16+
let mut group: BenchmarkGroup<'a> = BenchmarkGroup::default();
1717
register(&mut group);
1818
group.run().expect("Benchmark group execution has failed");
1919
}
2020

2121
/// Type-erased function that executes a single benchmark.
22-
type BenchmarkFn = Box<dyn Fn() -> anyhow::Result<BenchmarkStats>>;
22+
type BenchmarkFn<'a> = Box<dyn Fn() -> anyhow::Result<BenchmarkStats> + 'a>;
2323

2424
#[derive(Default)]
25-
pub struct BenchmarkGroup {
26-
benchmarks: HashMap<&'static str, BenchmarkFn>,
25+
pub struct BenchmarkGroup<'a> {
26+
benchmarks: HashMap<&'static str, BenchmarkFn<'a>>,
2727
}
2828

29-
impl BenchmarkGroup {
30-
pub fn new() -> Self {
31-
Self::default()
32-
}
33-
29+
impl<'a> BenchmarkGroup<'a> {
3430
/// Registers a single benchmark.
3531
///
3632
/// `constructor` returns a closure that will be benchmarked. This means
@@ -40,19 +36,19 @@ impl BenchmarkGroup {
4036
/// closure it produces each time will only be called once.
4137
pub fn register_benchmark<Ctor, Bench, R>(&mut self, name: &'static str, constructor: Ctor)
4238
where
43-
Ctor: Fn() -> Bench + Clone + 'static,
44-
Bench: FnOnce() -> R + 'static,
39+
Ctor: Fn() -> Bench + 'a,
40+
Bench: FnOnce() -> R,
4541
{
4642
// We want to type-erase the target `func` by wrapping it in a Box.
47-
let benchmark_fn = Box::new(move || benchmark_function(constructor.clone()));
43+
let benchmark_fn = Box::new(move || benchmark_function(&constructor));
4844
if self.benchmarks.insert(name, benchmark_fn).is_some() {
4945
panic!("Benchmark '{}' was registered twice", name);
5046
}
5147
}
5248

5349
/// Execute the benchmark group. It will parse CLI arguments and decide what to do based on
5450
/// them.
55-
pub fn run(self) -> anyhow::Result<()> {
51+
fn run(self) -> anyhow::Result<()> {
5652
raise_process_priority();
5753

5854
let args = parse_cli()?;

collector/benchlib/src/measure/perf_counter/linux.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ struct Counters {
1717
/// Benchmarks a single function generated by `benchmark_constructor`.
1818
/// The function is executed twice, once to gather wall-time measurement and the second time to
1919
/// gather perf. counters.
20-
pub fn benchmark_function<F: Fn() -> Bench + 'static, R, Bench: FnOnce() -> R + 'static>(
21-
benchmark_constructor: F,
20+
pub fn benchmark_function<F: Fn() -> Bench, R, Bench: FnOnce() -> R>(
21+
benchmark_constructor: &F,
2222
) -> anyhow::Result<BenchmarkStats> {
2323
let mut group = create_group()?;
2424
let counters = prepare_counters(&mut group)?;

collector/runtime-benchmarks/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,23 @@
22
This directory contains various pieces of code for which we measure how fast do they execute
33
when they are compiled with a specific version of `rustc`.
44

5-
The benchmarks are located in crates that are part of the `runtime-benchmarks` workspace. Each crate
6-
contains a set of benchmarks defined using named closures.
5+
The benchmarks are located in several crates (`benchmark groups`) located in this directory. Each
6+
group defines a set of benchmarks using named closures. Each group should have a single,
7+
default, binary target, which will be executed by `collector`, and it should use the
8+
`run_benchmark_group` function from [`benchlib`](../benchlib) to define the benchmarks.
79

8-
Benchmarks are divided into sub-crates so that some benchmarks can use various versions of dependency
9-
crates and also so that they are grouped together by a relevant area (e.g. hashmap benchmarks).
10+
Runtime benchmarks are divided into groups so that some benchmarks can use different versions of
11+
dependency crates and also so that they are grouped together by a relevant area
12+
(e.g. hashmap benchmarks).
13+
14+
## How are benchmarks executed
15+
The `collector` compiles each benchmark group and then invokes it with the `list` argument to list
16+
all benchmarks contained in the group.
17+
18+
Then it executes each group with the `run` argument, which will cause `benchlib` to actually perform
19+
the benchmarks and output the results to `stdout` in JSON. That means that the benchmarked code should
20+
not print anything to `stdout`!
21+
22+
Note that each benchmark group binary will be thus invoked twice per benchmarking collection. Keep this
23+
in mind so that the `main` function of the group doesn't perform too much work, which will be thrown
24+
away when it is invoked with the `list` argument.

collector/runtime-benchmarks/bufreader/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ use std::io::{BufRead, BufReader, Write};
55
const BYTES: usize = 64 * 1024 * 1024;
66

77
fn main() {
8+
let data = vec![0u8; BYTES];
9+
810
// Inspired by https://github.com/rust-lang/rust/issues/102727
911
// The pattern we want is a BufReader which wraps a Read impl where one Read::read call will
1012
// never fill the whole BufReader buffer.
1113
run_benchmark_group(|group| {
1214
group.register_benchmark("bufreader_snappy", || {
13-
let data = vec![0u8; BYTES];
14-
move || {
15+
|| {
1516
let mut compressed = Vec::new();
1617
FrameEncoder::new(&mut compressed)
17-
.write_all(&data[..])
18+
.write_all(data.as_slice())
1819
.unwrap();
1920
let mut reader =
2021
BufReader::with_capacity(BYTES, FrameDecoder::new(&compressed[..]));
@@ -27,6 +28,7 @@ fn main() {
2728
let len = buf.len();
2829
reader.consume(len);
2930
}
31+
compressed
3032
}
3133
});
3234
});
Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1+
use fxhash::FxBuildHasher;
2+
use hashbrown::HashMap;
3+
14
use benchlib;
25
use benchlib::benchmark::{black_box, run_benchmark_group};
36

7+
fn create_map_1m_integers() -> HashMap<u64, u64, FxBuildHasher> {
8+
let mut map: HashMap<u64, u64, _> =
9+
HashMap::with_capacity_and_hasher(1_000_000, FxBuildHasher::default());
10+
for index in 0..map.capacity() {
11+
map.insert(index as u64, index as u64);
12+
}
13+
map
14+
}
15+
416
fn main() {
17+
let map_1m_integers = create_map_1m_integers();
18+
519
run_benchmark_group(|group| {
620
// Measures how long does it take to insert 1 million numbers into a hashmap.
721
group.register_benchmark("hashmap_insert_1m", || {
822
let count = 1_000_000;
9-
let mut map = hashbrown::HashMap::with_capacity_and_hasher(
23+
let mut map = HashMap::with_capacity_and_hasher(
1024
// Over allocate the hashmap to avoid reallocations when inserting
1125
count * 2,
12-
fxhash::FxBuildHasher::default(),
26+
FxBuildHasher::default(),
1327
);
1428
move || {
1529
for index in 0..count {
@@ -20,66 +34,37 @@ fn main() {
2034

2135
// Measures how long it takes to remove 1 million elements from a hashmap.
2236
group.register_benchmark("hashmap_remove_1m", || {
23-
let mut map = hashbrown::HashMap::with_capacity_and_hasher(
24-
1_000_000,
25-
fxhash::FxBuildHasher::default(),
26-
);
27-
for index in 0..map.capacity() {
28-
map.insert(index, index);
29-
}
30-
37+
let mut map = create_map_1m_integers();
3138
move || {
3239
for index in 0..map.capacity() {
33-
map.remove(&index);
40+
map.remove(&(index as u64));
3441
}
3542
}
3643
});
3744

3845
// Measures how long it takes to find 1 million elements that are in a hashmap.
3946
group.register_benchmark("hashmap_find_1m", || {
40-
let mut map = hashbrown::HashMap::with_capacity_and_hasher(
41-
1_000_000,
42-
fxhash::FxBuildHasher::default(),
43-
);
44-
for index in 0..map.capacity() {
45-
map.insert(index, index);
46-
}
47-
48-
move || {
47+
|| {
48+
let map = &map_1m_integers;
4949
for index in 0..map.capacity() {
50-
black_box(map.get(&index));
50+
black_box(map.get(&(index as u64)));
5151
}
5252
}
5353
});
5454

5555
// Measures how long it takes to find 1 million elements that are not in a hashmap.
5656
group.register_benchmark("hashmap_find_misses_1m", || {
57-
let mut map = hashbrown::HashMap::with_capacity_and_hasher(
58-
1_000_000,
59-
fxhash::FxBuildHasher::default(),
60-
);
61-
for index in 0..map.capacity() {
62-
map.insert(index, index);
63-
}
64-
65-
move || {
57+
|| {
58+
let map = &map_1m_integers;
6659
for index in map.capacity()..(map.capacity() * 2) {
67-
black_box(map.get(&index));
60+
black_box(map.get(&(index as u64)));
6861
}
6962
}
7063
});
7164

7265
// Measures how long it takes to iterate through values of a hashmap with 1 million elements.
7366
group.register_benchmark("hashmap_iterate_1m", || {
74-
let mut map = hashbrown::HashMap::with_capacity_and_hasher(
75-
1_000_000,
76-
fxhash::FxBuildHasher::default(),
77-
);
78-
for index in 0..map.capacity() {
79-
map.insert(index, index as u64);
80-
}
81-
82-
move || map.values().sum::<u64>()
67+
|| map_1m_integers.values().sum::<u64>()
8368
});
8469
});
8570
}

collector/runtime-benchmarks/raytracer/src/main.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use benchlib::benchmark::run_benchmark_group;
22
use camera::Camera;
33
use materials::{Dielectric, Lambertian, Material, Metal};
44
use model::{Model, Sphere};
5-
use std::rc::Rc;
65
use vec::{random_in_unit_disc, Vec3};
76

87
mod camera; // translate 2D pixel coordinates to 3D rays
@@ -78,26 +77,27 @@ fn main() {
7877

7978
const NSAMPLES: usize = 100;
8079

80+
let scene = create_scene();
81+
let lookfrom = Vec3(20.0 * 0.47f32.cos(), 20.0 * 0.47f32.sin(), 3.0);
82+
let lookat = Vec3(0.0, 0.0, 1.0);
83+
let vup = Vec3(0.0, 0.0, 1.0);
84+
let focus_distance = (lookfrom - lookat).length();
85+
let aperture = 0.3;
86+
let camera = Camera::new(
87+
lookfrom,
88+
lookat,
89+
vup,
90+
20.0,
91+
WIDTH as f32 / HEIGHT as f32,
92+
aperture,
93+
focus_distance,
94+
);
95+
8196
run_benchmark_group(|group| {
8297
// Performs raytracing on a simple scene.
8398
// Adapted from https://github.com/jorendorff/rust-raytrace.
8499
group.register_benchmark("raytracer", || {
85-
let scene = create_scene();
86-
let lookfrom = Vec3(20.0 * 0.47f32.cos(), 20.0 * 0.47f32.sin(), 3.0);
87-
let lookat = Vec3(0.0, 0.0, 1.0);
88-
let vup = Vec3(0.0, 0.0, 1.0);
89-
let focus_distance = (lookfrom - lookat).length();
90-
let aperture = 0.3;
91-
let camera = Rc::new(Camera::new(
92-
lookfrom,
93-
lookat,
94-
vup,
95-
20.0,
96-
WIDTH as f32 / HEIGHT as f32,
97-
aperture,
98-
focus_distance,
99-
));
100-
move || render::render(&*scene, &camera, WIDTH, HEIGHT, NSAMPLES)
100+
|| render::render(scene.as_ref(), &camera, WIDTH, HEIGHT, NSAMPLES)
101101
});
102102
});
103103
}

collector/runtime-benchmarks/text-search/src/main.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ use benchlib::benchmark::run_benchmark_group;
55
const TEXT_SHERLOCK: &str = include_str!("data/sherlock.txt");
66

77
fn main() {
8+
let regex1 = Regex::new(r"[a-zA-Z]+ing").unwrap();
9+
let regex2 = Regex::new(r"(Sherlock|Holmes|Watson|Irene|Adler|John|Baker)").unwrap();
10+
811
run_benchmark_group(|group| {
912
group.register_benchmark("regex-search-1", || {
10-
let regex = Regex::new(r"[a-zA-Z]+ing").unwrap();
11-
move || regex.find_iter(TEXT_SHERLOCK).count()
13+
|| regex1.find_iter(TEXT_SHERLOCK).count()
1214
});
1315
group.register_benchmark("regex-capture-1", || {
14-
let regex = Regex::new(r"(Sherlock|Holmes|Watson|Irene|Adler|John|Baker)").unwrap();
15-
move || regex.captures_iter(TEXT_SHERLOCK).count()
16+
|| regex2.captures_iter(TEXT_SHERLOCK).count()
1617
});
1718
});
1819
}

0 commit comments

Comments
 (0)