Skip to content

Commit 04fcede

Browse files
authored
Merge pull request #1453 from rust-lang/runtime-cli
Runtime benchmarks CLI and documentation improvements
2 parents 9e8c08f + 49bab72 commit 04fcede

File tree

14 files changed

+159
-60
lines changed

14 files changed

+159
-60
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Additional documentation on running and setting up the frontend and backend can
88
be found in the `README` files in the `collector` and `site` directories.
99

1010
Additional documentation on the benchmark programs can be found in the `README`
11-
file in the `collector/benchmarks` directory.
11+
file in the `collector/compile-benchmarks` and `collector/runtime-benchmarks` directories.

collector/benchlib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ serde_json = "1.0.83"
1313
log = "0.4.17"
1414
env_logger = "0.9.0"
1515
clap = { version = "3.2", features = ["derive"] }
16+
libc = "0.2"
1617

1718
[target.'cfg(unix)'.dependencies]
1819
perf-event = "0.4.7"

collector/benchlib/src/benchmark.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::cli::{parse_cli, Args, BenchmarkArgs};
22
use crate::measure::benchmark_function;
33
use crate::messages::BenchmarkResult;
4+
use crate::process::raise_process_priority;
45
use log::LevelFilter;
56
use std::collections::HashMap;
67

@@ -14,9 +15,16 @@ pub fn benchmark_suite<F: FnOnce(&mut BenchmarkSuite)>(define_func: F) {
1415
suite.run().expect("Benchmark suite has failed");
1516
}
1617

18+
/// Type-erased function that performs a benchmark.
19+
struct BenchmarkWrapper {
20+
func: Box<dyn Fn() -> anyhow::Result<BenchmarkResult>>,
21+
}
22+
23+
type BenchmarkMap = HashMap<&'static str, BenchmarkWrapper>;
24+
1725
#[derive(Default)]
1826
pub struct BenchmarkSuite {
19-
benchmarks: HashMap<&'static str, BenchmarkWrapper>,
27+
benchmarks: BenchmarkMap,
2028
}
2129

2230
impl BenchmarkSuite {
@@ -48,36 +56,47 @@ impl BenchmarkSuite {
4856
/// Execute the benchmark suite. It will parse CLI arguments and decide what to do based on
4957
/// them.
5058
pub fn run(self) -> anyhow::Result<()> {
59+
raise_process_priority();
60+
5161
let args = parse_cli()?;
5262
match args {
5363
Args::Benchmark(args) => {
54-
self.run_benchmark(args)?;
64+
run_benchmark(args, self.benchmarks)?;
5565
}
5666
}
5767

5868
Ok(())
5969
}
70+
}
6071

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);
72+
fn run_benchmark(args: BenchmarkArgs, benchmarks: BenchmarkMap) -> anyhow::Result<()> {
73+
let mut items: Vec<(&'static str, BenchmarkWrapper)> = benchmarks
74+
.into_iter()
75+
.filter(|(name, _)| passes_filter(name, args.exclude.as_deref(), args.include.as_deref()))
76+
.collect();
77+
items.sort_unstable_by_key(|item| item.0);
6478

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-
}
79+
let mut results: Vec<BenchmarkResult> = Vec::with_capacity(items.len());
80+
for (name, def) in items {
81+
for i in 0..args.iterations {
82+
let result = (def.func)()?;
83+
log::info!("Benchmark (run {i}) `{name}` completed: {result:?}");
84+
results.push(result);
7285
}
73-
74-
println!("{}", serde_json::to_string(&results)?);
75-
Ok(())
7686
}
87+
88+
println!("{}", serde_json::to_string(&results)?);
89+
Ok(())
7790
}
7891

79-
struct BenchmarkWrapper {
80-
func: Box<dyn Fn() -> anyhow::Result<BenchmarkResult>>,
92+
/// Tests if the name of the benchmark passes through the include and exclude filter flags.
93+
fn passes_filter(name: &str, exclude: Option<&str>, include: Option<&str>) -> bool {
94+
match (exclude, include) {
95+
(Some(exclude), Some(include)) => name.starts_with(include) && !name.starts_with(exclude),
96+
(None, Some(include)) => name.starts_with(include),
97+
(Some(exclude), None) => !name.starts_with(&exclude),
98+
(None, None) => true,
99+
}
81100
}
82101

83102
/// Copied from `iai`, so that we don't have to use unstable features.

collector/benchlib/src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ pub struct BenchmarkArgs {
1111
/// How many times should each benchmark be repeated.
1212
#[clap(long, default_value = "5")]
1313
pub iterations: u32,
14+
15+
/// Exclude all benchmarks matching a prefix in this comma-separated list
16+
#[clap(long)]
17+
pub exclude: Option<String>,
18+
19+
/// Include only benchmarks matching a prefix in this comma-separated list
20+
#[clap(long)]
21+
pub include: Option<String>,
1422
}
1523

1624
pub fn parse_cli() -> anyhow::Result<Args> {

collector/benchlib/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
//! This library defines an API for performing benchmarks of Rust code. It is tailored for the
2-
//! use-case of `rustc-perf`, that's why we don't use e.g. `criterion` or `iai`.
1+
//! This library defines an API for performing benchmarks of Rust code and various other utilities
2+
//! for measuring and benchmarking. It is tailored for the use-case of `rustc-perf`, that's why we
3+
//! don't use e.g. `criterion` or `iai`.
34
//!
45
//! We want to be able to define short benchmarks in code, measure specific perf. counters and most
56
//! importantly, consume the benchmark results in a programmatic way.
@@ -16,3 +17,4 @@ pub mod benchmark;
1617
mod cli;
1718
pub mod measure;
1819
pub mod messages;
20+
pub mod process;

collector/benchlib/src/process.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#[cfg(unix)]
2+
pub fn raise_process_priority() {
3+
unsafe {
4+
// Try to reduce jitter in wall time by increasing our priority to the
5+
// maximum
6+
for i in (1..21).rev() {
7+
let r = libc::setpriority(libc::PRIO_PROCESS as _, libc::getpid() as libc::id_t, -i);
8+
if r == 0 {
9+
break;
10+
}
11+
}
12+
}
13+
}
14+
15+
#[cfg(windows)]
16+
pub fn raise_process_priority() {}

collector/compile-benchmarks/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# The Benchmark Suite
1+
# The Compile-time Benchmark Suite
22

3-
This file describes the programs in the benchmark suite and explains why they
3+
This file describes the programs in the compile-time benchmark suite and explains why they
44
were included.
55

66
The suite changes over time. Sometimes the code for a benchmark is updated, in
77
which case a small suffix will be added (starting with "-2", then "-3", and so
88
on.)
99

10-
There are three categories of benchmarks, **Primary**, **Secondary**, and
10+
There are three categories of compile-time benchmarks, **Primary**, **Secondary**, and
1111
**Stable**.
1212

1313
## Primary
@@ -198,7 +198,7 @@ Rust code being written today.
198198
applies correctly, e.g. `target/release/collector bench_local +nightly
199199
--id Test --profiles=Check --scenarios=IncrPatched
200200
--include=$NEW_BENCHMARK`
201-
- Add the new entry to `collector/benchmarks/README.md`.
201+
- Add the new entry to `collector/compile-benchmarks/README.md`.
202202
- `git add` the `Cargo.lock` file, if it's not already part of the
203203
benchmark's committed code.
204204
- If the benchmark has a `.gitignore` file that contains `Cargo.lock`,
@@ -232,7 +232,7 @@ Rust code being written today.
232232
- In the first commit just remove the old code.
233233
- Do this with `git rm -r` on the directory.
234234
- In the second commit do everything else.
235-
- Remove the entry from `collector/benchmarks/README.md`.
235+
- Remove the entry from `collector/compile-benchmarks/README.md`.
236236
- `git grep` for occurrences of the old benchmark name (e.g. in
237237
`.github/workflows/ci.yml` or `ci/check-*.sh`) and see if anything needs
238238
changing... usually not.

collector/runtime-benchmarks/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# The Runtime Benchmark Suite
2+
This directory contains various pieces of code for which we measure how fast do they execute
3+
when they are compiled with a specific version of `rustc`.
4+
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.
7+
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).

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use benchlib::benchmark::BenchmarkSuite;
44
fn main() {
55
let mut suite = BenchmarkSuite::new();
66

7+
/// Measures how long does it take to insert 10 thousand numbers into a `hashbrown` hashmap.
78
suite.register("hashmap-insert-10k", || {
89
let mut map =
910
hashbrown::HashMap::with_capacity_and_hasher(10000, fxhash::FxBuildHasher::default());

collector/src/bin/collector.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,19 @@ struct BenchRustcOption {
562562
enum Commands {
563563
/// Benchmarks the performance of programs generated by a local rustc
564564
BenchRuntimeLocal {
565+
/// The path to the local rustc to measure
565566
rustc: String,
566567
/// Identifier to associate benchmark results with
567568
#[clap(long)]
568569
id: Option<String>,
570+
571+
/// Exclude all benchmarks matching a prefix in this comma-separated list
572+
#[clap(long)]
573+
exclude: Option<String>,
574+
575+
/// Include only benchmarks matching a prefix in this comma-separated list
576+
#[clap(long)]
577+
include: Option<String>,
569578
},
570579
/// Benchmarks a local rustc
571580
BenchLocal {
@@ -692,8 +701,19 @@ fn main_result() -> anyhow::Result<i32> {
692701
let target_triple = format!("{}-unknown-linux-gnu", std::env::consts::ARCH);
693702

694703
match args.command {
695-
Commands::BenchRuntimeLocal { rustc, id } => {
696-
bench_runtime(&rustc, id.as_deref(), runtime_benchmark_dir)?;
704+
Commands::BenchRuntimeLocal {
705+
rustc,
706+
id,
707+
exclude,
708+
include,
709+
} => {
710+
bench_runtime(
711+
&rustc,
712+
id.as_deref(),
713+
exclude,
714+
include,
715+
runtime_benchmark_dir,
716+
)?;
697717
Ok(0)
698718
}
699719
Commands::BenchLocal {

collector/src/bin/rustc-fake.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn main() {
6868
let wrapper = args.remove(pos);
6969
let wrapper = wrapper.to_str().unwrap();
7070

71-
raise_priority();
71+
benchlib::process::raise_process_priority();
7272

7373
// These strings come from `PerfTool::name()`.
7474
match wrapper {
@@ -479,20 +479,6 @@ fn exec(cmd: &mut Command) -> ! {
479479
panic!("failed to exec `{}`: {}", cmd_d, error);
480480
}
481481

482-
#[cfg(unix)]
483-
fn raise_priority() {
484-
unsafe {
485-
// Try to reduce jitter in wall time by increasing our priority to the
486-
// maximum
487-
for i in (1..21).rev() {
488-
let r = libc::setpriority(libc::PRIO_PROCESS as _, libc::getpid() as libc::id_t, -i);
489-
if r == 0 {
490-
break;
491-
}
492-
}
493-
}
494-
}
495-
496482
#[cfg(unix)]
497483
fn print_memory() {
498484
use std::mem;
@@ -544,8 +530,5 @@ fn run_summarize(name: &str, prof_out_dir: &Path, prefix: &str) -> anyhow::Resul
544530
fs::read_to_string(&json).with_context(|| format!("failed to read {:?}", json))
545531
}
546532

547-
#[cfg(windows)]
548-
fn raise_priority() {}
549-
550533
#[cfg(windows)]
551534
fn print_memory() {}

collector/src/runtime.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,71 @@ struct BenchmarkBinary {
1010
path: PathBuf,
1111
}
1212

13-
pub fn bench_runtime(rustc: &str, id: Option<&str>, benchmark_dir: PathBuf) -> anyhow::Result<()> {
13+
/// Perform a series of runtime benchmarks using the provided `rustc` compiler.
14+
/// The runtime benchmarks are looked up in `benchmark_dir`, which is expected to be a path
15+
/// to a Cargo crate. All binaries built by that crate will are expected to be runtime benchmark
16+
/// suites that leverage `benchlib`.
17+
pub fn bench_runtime(
18+
rustc: &str,
19+
id: Option<&str>,
20+
exclude: Option<String>,
21+
include: Option<String>,
22+
benchmark_dir: PathBuf,
23+
) -> anyhow::Result<()> {
1424
let toolchain = get_local_toolchain(&[Profile::Opt], rustc, None, None, id, "")?;
1525
let output = compile_runtime_benchmarks(&toolchain, &benchmark_dir)?;
1626
let binaries = gather_binaries(&output)?;
1727

1828
for binary in binaries {
1929
let name = binary.path.file_name().and_then(|s| s.to_str()).unwrap();
2030

21-
let result = Command::new(&binary.path).arg("benchmark").output()?;
22-
if !result.status.success() {
23-
anyhow::bail!(
24-
"Failed to run runtime benchmark {name}\n{}\n{}",
25-
String::from_utf8_lossy(&result.stdout),
26-
String::from_utf8_lossy(&result.stderr)
27-
);
28-
} else {
29-
log::info!("Successfully ran runtime benchmark {name}",);
30-
31-
let data: Vec<BenchmarkResult> = serde_json::from_slice(&result.stdout)?;
32-
// TODO: do something with the result
33-
println!("{name}: {:?}", data);
34-
}
31+
let data: Vec<BenchmarkResult> =
32+
execute_runtime_binary(&binary.path, name, exclude.as_deref(), include.as_deref())?;
33+
// TODO: do something with the result
34+
println!("{name}: {:?}", data);
3535
}
3636

3737
Ok(())
3838
}
3939

40+
/// Execute a single runtime benchmark suite defined in a binary crate located in
41+
/// `runtime-benchmarks`. The binary is expected to use benchlib's `BenchmarkSuite` to execute
42+
/// a set of runtime benchmarks and return a list of `BenchmarkResult`s encoded as JSON.
43+
fn execute_runtime_binary(
44+
binary: &Path,
45+
name: &str,
46+
exclude: Option<&str>,
47+
include: Option<&str>,
48+
) -> anyhow::Result<Vec<BenchmarkResult>> {
49+
// Turn off ASLR
50+
let mut command = Command::new("setarch");
51+
command.arg(std::env::consts::ARCH);
52+
command.arg("-R");
53+
command.arg(binary);
54+
command.arg("benchmark");
55+
56+
if let Some(exclude) = exclude {
57+
command.args(&["--exclude", exclude]);
58+
}
59+
if let Some(include) = include {
60+
command.args(&["--include", include]);
61+
}
62+
63+
let result = command.output()?;
64+
65+
if !result.status.success() {
66+
return Err(anyhow::anyhow!(
67+
"Failed to run runtime benchmark {name}\n{}\n{}",
68+
String::from_utf8_lossy(&result.stdout),
69+
String::from_utf8_lossy(&result.stderr)
70+
));
71+
}
72+
73+
log::info!("Successfully ran runtime benchmark {name}");
74+
75+
Ok(serde_json::from_slice(&result.stdout)?)
76+
}
77+
4078
/// Compiles all runtime benchmarks and returns the stdout output of Cargo.
4179
fn compile_runtime_benchmarks(toolchain: &LocalToolchain, dir: &Path) -> anyhow::Result<Vec<u8>> {
4280
let result = Command::new(&toolchain.cargo)

0 commit comments

Comments
 (0)