Skip to content

Commit 9459ec2

Browse files
authored
Merge pull request #1695 from Kobzol/runtime-cachegrind-diff
Add runtime benchmark cachegrind diff
2 parents f79a3b1 + 90118b8 commit 9459ec2

File tree

8 files changed

+234
-165
lines changed

8 files changed

+234
-165
lines changed

Cargo.lock

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

collector/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ flate2 = { version = "1.0.22", features = ["rust_backend"] }
3434
rayon = "1.5.2"
3535
cargo_metadata = "0.15.0"
3636
thousands = "0.2.0"
37+
rustc-demangle = { version = "0.1", features = ["std"] }
3738

3839
benchlib = { path = "benchlib" }
3940

collector/src/bin/collector.rs

Lines changed: 59 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::io::BufWriter;
2323
use std::io::Write;
2424
use std::path::{Path, PathBuf};
2525
use std::process;
26-
use std::process::{Command, Stdio};
26+
use std::process::Command;
2727
use std::time::Duration;
2828
use std::{str, time::Instant};
2929
use tokio::runtime::Runtime;
@@ -39,7 +39,8 @@ use collector::runtime::{profile_runtime, RuntimeCompilationOpts};
3939
use collector::toolchain::{
4040
create_toolchain_from_published_version, get_local_toolchain, Sysroot, Toolchain,
4141
};
42-
use collector::utils::wait_for_future;
42+
use collector::utils::cachegrind::cachegrind_diff;
43+
use collector::utils::{is_installed, wait_for_future};
4344

4445
fn n_normal_benchmarks_remaining(n: usize) -> String {
4546
let suffix = if n == 1 { "" } else { "s" };
@@ -123,10 +124,6 @@ fn check_installed(name: &str) -> anyhow::Result<()> {
123124
Ok(())
124125
}
125126

126-
fn is_installed(name: &str) -> bool {
127-
Command::new(name).output().is_ok()
128-
}
129-
130127
fn generate_cachegrind_diffs(
131128
id1: &str,
132129
id2: &str,
@@ -161,27 +158,9 @@ fn generate_cachegrind_diffs(
161158
let id_diff = format!("{}-{}", id1, id2);
162159
let cgout1 = out_dir.join(filename("cgout", id1));
163160
let cgout2 = out_dir.join(filename("cgout", id2));
164-
let cgfilt1 = out_dir.join(filename("cgfilt", id1));
165-
let cgfilt2 = out_dir.join(filename("cgfilt", id2));
166-
let cgfilt_diff = out_dir.join(filename("cgfilt-diff", &id_diff));
167161
let cgann_diff = out_dir.join(filename("cgann-diff", &id_diff));
168162

169-
if let Err(e) = rustfilt(&cgout1, &cgfilt1) {
170-
errors.incr();
171-
eprintln!("collector error: {:?}", e);
172-
continue;
173-
}
174-
if let Err(e) = rustfilt(&cgout2, &cgfilt2) {
175-
errors.incr();
176-
eprintln!("collector error: {:?}", e);
177-
continue;
178-
}
179-
if let Err(e) = cg_diff(&cgfilt1, &cgfilt2, &cgfilt_diff) {
180-
errors.incr();
181-
eprintln!("collector error: {:?}", e);
182-
continue;
183-
}
184-
if let Err(e) = cg_annotate(&cgfilt_diff, &cgann_diff) {
163+
if let Err(e) = cachegrind_diff(&cgout1, &cgout2, &cgann_diff) {
185164
errors.incr();
186165
eprintln!("collector error: {:?}", e);
187166
continue;
@@ -194,68 +173,6 @@ fn generate_cachegrind_diffs(
194173
annotated_diffs
195174
}
196175

197-
/// Demangles symbols in a file using rustfilt and writes result to path.
198-
fn rustfilt(cgout: &Path, path: &Path) -> anyhow::Result<()> {
199-
if !is_installed("rustfilt") {
200-
anyhow::bail!("`rustfilt` not installed.");
201-
}
202-
let output = Command::new("rustfilt")
203-
.arg("-i")
204-
.arg(cgout)
205-
.stderr(Stdio::inherit())
206-
.output()
207-
.context("failed to run `rustfilt`")?;
208-
209-
if !output.status.success() {
210-
anyhow::bail!("failed to process output with rustfilt");
211-
}
212-
213-
fs::write(path, output.stdout).context("failed to write `rustfilt` output")?;
214-
215-
Ok(())
216-
}
217-
218-
/// Compares two Cachegrind output files using cg_diff and writes result to path.
219-
fn cg_diff(cgout1: &Path, cgout2: &Path, path: &Path) -> anyhow::Result<()> {
220-
if !is_installed("cg_diff") {
221-
anyhow::bail!("`cg_diff` not installed.");
222-
}
223-
let output = Command::new("cg_diff")
224-
.arg(r"--mod-filename=s/\/rustc\/[^\/]*\///")
225-
.arg("--mod-funcname=s/[.]llvm[.].*//")
226-
.arg(cgout1)
227-
.arg(cgout2)
228-
.stderr(Stdio::inherit())
229-
.output()
230-
.context("failed to run `cg_diff`")?;
231-
232-
if !output.status.success() {
233-
anyhow::bail!("failed to generate cachegrind diff");
234-
}
235-
236-
fs::write(path, output.stdout).context("failed to write `cg_diff` output")?;
237-
238-
Ok(())
239-
}
240-
241-
/// Post process Cachegrind output file and writes resutl to path.
242-
fn cg_annotate(cgout: &Path, path: &Path) -> anyhow::Result<()> {
243-
let output = Command::new("cg_annotate")
244-
.arg("--show-percs=no")
245-
.arg(cgout)
246-
.stderr(Stdio::inherit())
247-
.output()
248-
.context("failed to run `cg_annotate`")?;
249-
250-
if !output.status.success() {
251-
anyhow::bail!("failed to annotate cachegrind output");
252-
}
253-
254-
fs::write(path, output.stdout).context("failed to write `cg_annotate` output")?;
255-
256-
Ok(())
257-
}
258-
259176
#[allow(clippy::too_many_arguments)]
260177
fn profile_compile(
261178
toolchain: &Toolchain,
@@ -527,6 +444,10 @@ enum Commands {
527444
/// The path to the local rustc used to compile the runtime benchmark
528445
rustc: String,
529446

447+
/// The path to a second local rustc used to compare with the baseline rustc
448+
#[arg(long)]
449+
rustc2: Option<String>,
450+
530451
/// Name of the benchmark that should be profiled
531452
benchmark: String,
532453
},
@@ -716,21 +637,59 @@ fn main_result() -> anyhow::Result<i32> {
716637
runtime,
717638
profiler,
718639
rustc,
640+
rustc2,
719641
benchmark,
720642
} => {
721-
let toolchain =
722-
get_local_toolchain(&[Profile::Opt], &rustc, None, None, None, "", target_triple)?;
723-
let suite = prepare_runtime_benchmark_suite(
724-
&toolchain,
725-
&runtime_benchmark_dir,
726-
CargoIsolationMode::Cached,
727-
runtime.group,
728-
// Compile with debuginfo to have filenames and line numbers available in the
729-
// generated profiles.
730-
RuntimeCompilationOpts::default().debug_info("1"),
731-
)?
732-
.suite;
733-
profile_runtime(profiler, suite, &benchmark)?;
643+
let get_suite = |rustc: &str, id: &str| {
644+
let toolchain = get_local_toolchain(
645+
&[Profile::Opt],
646+
rustc,
647+
None,
648+
None,
649+
None,
650+
id,
651+
target_triple.clone(),
652+
)?;
653+
let suite = prepare_runtime_benchmark_suite(
654+
&toolchain,
655+
&runtime_benchmark_dir,
656+
CargoIsolationMode::Cached,
657+
runtime.group.clone(),
658+
// Compile with debuginfo to have filenames and line numbers available in the
659+
// generated profiles.
660+
RuntimeCompilationOpts::default().debug_info("1"),
661+
)?
662+
.suite;
663+
Ok::<_, anyhow::Error>((toolchain, suite))
664+
};
665+
666+
println!("Profiling {rustc}");
667+
let (toolchain1, suite1) = get_suite(&rustc, "1")?;
668+
let profile1 = profile_runtime(profiler.clone(), &toolchain1, suite1, &benchmark)?;
669+
670+
if let Some(rustc2) = rustc2 {
671+
match profiler {
672+
RuntimeProfiler::Cachegrind => {
673+
println!("Profiling {rustc2}");
674+
let (toolchain2, suite2) = get_suite(&rustc2, "2")?;
675+
let profile2 = profile_runtime(profiler, &toolchain2, suite2, &benchmark)?;
676+
677+
let output = profile1.parent().unwrap().join(format!(
678+
"cgann-diff-{}-{}-{benchmark}",
679+
toolchain1.id, toolchain2.id
680+
));
681+
cachegrind_diff(&profile1, &profile2, &output)
682+
.context("Cannot generate Cachegrind diff")?;
683+
println!("Cachegrind diff stored in `{}`", output.display());
684+
}
685+
}
686+
} else {
687+
println!(
688+
"Profiling complete, result can be found in `{}`",
689+
profile1.display()
690+
);
691+
}
692+
734693
Ok(0)
735694
}
736695
Commands::BenchLocal {
@@ -971,7 +930,6 @@ fn main_result() -> anyhow::Result<i32> {
971930
if profiler == Profiler::Cachegrind {
972931
check_installed("valgrind")?;
973932
check_installed("cg_annotate")?;
974-
check_installed("rustfilt")?;
975933

976934
let diffs = generate_cachegrind_diffs(
977935
&id1,

collector/src/compile/execute/profiler.rs

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::compile::execute::{PerfTool, ProcessOutputData, Processor, Retry};
22
use crate::utils;
3+
use crate::utils::cachegrind::cachegrind_annotate;
34
use anyhow::Context;
45
use std::collections::HashMap;
56
use std::future::Future;
@@ -229,53 +230,7 @@ impl<'a> Processor for ProfileProcessor<'a> {
229230
let cgout_file = filepath(self.output_dir, &out_file("cgout"));
230231
let cgann_file = filepath(self.output_dir, &out_file("cgann"));
231232

232-
// It's useful to filter all `file:function` entries from
233-
// jemalloc into a single fake
234-
// `<all-jemalloc-files>:<all-jemalloc-functions>` entry. That
235-
// way the cost of all allocations is visible in one line,
236-
// rather than spread across many small entries.
237-
//
238-
// The downside is that we don't get any annotations within
239-
// jemalloc source files, but this is no real loss, given that
240-
// jemalloc is basically a black box whose code we never look
241-
// at anyway. DHAT is the best way to profile allocations.
242-
let reader = io::BufReader::new(fs::File::open(&tmp_cgout_file)?);
243-
let mut writer = io::BufWriter::new(fs::File::create(&cgout_file)?);
244-
let mut in_jemalloc_file = false;
245-
246-
// A Cachegrind profile contains `fn=<function-name>` lines,
247-
// `fl=<filename>` lines, and everything else. We just need to
248-
// modify the `fn=` and `fl=` lines that refer to jemalloc
249-
// code.
250-
for line in reader.lines() {
251-
let line = line?;
252-
if line.starts_with("fl=") {
253-
// All jemalloc filenames have `/jemalloc/` or
254-
// something like `/jemalloc-sys-1e20251078fe5355/` in
255-
// them.
256-
in_jemalloc_file = line.contains("/jemalloc");
257-
if in_jemalloc_file {
258-
writeln!(writer, "fl=<all-jemalloc-files>")?;
259-
continue;
260-
}
261-
} else if line.starts_with("fn=") {
262-
// Any function within a jemalloc file is a jemalloc
263-
// function.
264-
if in_jemalloc_file {
265-
writeln!(writer, "fn=<all-jemalloc-functions>")?;
266-
continue;
267-
}
268-
}
269-
writeln!(writer, "{}", line)?;
270-
}
271-
writer.flush()?;
272-
273-
let mut cg_annotate_cmd = Command::new("cg_annotate");
274-
cg_annotate_cmd
275-
.arg("--auto=yes")
276-
.arg("--show-percs=yes")
277-
.arg(&cgout_file);
278-
fs::write(cgann_file, cg_annotate_cmd.output()?.stdout)?;
233+
cachegrind_annotate(&tmp_cgout_file, &cgout_file, &cgann_file)?;
279234
}
280235

281236
// Callgrind produces (via rustc-fake) a data file called `clgout`.

collector/src/runtime/profile.rs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,53 @@
11
use std::fs::create_dir_all;
2-
use std::path::Path;
2+
use std::path::{Path, PathBuf};
33
use std::process::{Command, Stdio};
44

55
use anyhow::Context;
66

77
use crate::command_output;
88
use crate::runtime::BenchmarkSuite;
9+
use crate::toolchain::Toolchain;
10+
use crate::utils::cachegrind::cachegrind_annotate;
911

1012
#[derive(Clone, Debug, clap::ValueEnum)]
1113
pub enum RuntimeProfiler {
1214
Cachegrind,
1315
}
1416

17+
/// Profiles a single runtime benchmark and returns a path to the result.
1518
pub fn profile_runtime(
1619
profiler: RuntimeProfiler,
20+
toolchain: &Toolchain,
1721
suite: BenchmarkSuite,
1822
benchmark: &str,
19-
) -> anyhow::Result<()> {
23+
) -> anyhow::Result<PathBuf> {
2024
let Some(group) = suite.get_group_by_benchmark(benchmark) else {
2125
return Err(anyhow::anyhow!("Benchmark `{benchmark}` not found"));
2226
};
2327

24-
let result_dir = Path::new("results");
28+
let result_dir = Path::new("results-runtime");
2529
create_dir_all(result_dir)?;
2630

27-
let (mut cmd, out_file) = match profiler {
31+
let out_file = match profiler {
2832
RuntimeProfiler::Cachegrind => {
29-
let out_file = result_dir.join(format!("cgout-{benchmark}"));
33+
let cgout_tmp = tempfile::NamedTempFile::new()?.into_temp_path();
34+
let cgout_file = result_dir.join(format!("cgout-{}-{benchmark}", toolchain.id));
35+
let cgann_file = result_dir.join(format!("cgann-{}-{benchmark}", toolchain.id));
36+
3037
let mut cmd = Command::new("valgrind");
3138
cmd.arg("--tool=cachegrind")
3239
.arg("--branch-sim=no")
3340
.arg("--cache-sim=no")
34-
.arg(format!("--cachegrind-out-file={}", out_file.display()));
35-
(cmd, out_file)
41+
.arg(format!("--cachegrind-out-file={}", cgout_tmp.display()));
42+
cmd.stdin(Stdio::null());
43+
cmd.arg(&group.binary).arg("profile").arg(benchmark);
44+
command_output(&mut cmd).context("Cannot run profiler")?;
45+
46+
cachegrind_annotate(&cgout_tmp, &cgout_file, &cgann_file)
47+
.context("Cannot annotate result")?;
48+
cgout_file
3649
}
3750
};
38-
cmd.stdin(Stdio::null());
39-
40-
cmd.arg(&group.binary).arg("profile").arg(benchmark);
41-
command_output(&mut cmd).context("Cannot run profiler")?;
42-
43-
println!(
44-
"Profiling complete, result can be found in `{}`",
45-
out_file.display()
46-
);
4751

48-
Ok(())
52+
Ok(out_file)
4953
}

0 commit comments

Comments
 (0)