Guide: profile a runtime benchmark

中文版:profiling_CN.md

Profile a runtime benchmark to find where time is spent. Two profilers are supported: Cachegrind (instruction-level, Valgrind-based) and perf-record (sampling-based, Linux perf). Cachegrind also supports diffing two rustc builds.

Prerequisites

  • Everything in local-benchmarking prerequisites.
  • For cachegrind: valgrind, cg_annotate, cg_diff on PATH. Build a recent Valgrind; the precise-cachegrind feature additionally needs DEP_VALGRIND=<path-to-valgrind>/include at build time.
  • For perf-record: perf on PATH.

Verify:

valgrind --version && cg_annotate --version && perf --version

Step 1 — (optional) enable precise cachegrind

Precise cachegrind wraps the measured function with Valgrind client requests to start/stop instrumentation exactly around bench(env), removing the constructor/warm-up from the profile. Build the collector and benchmark crates with the feature:

DEP_VALGRIND=/usr/include cargo build --release --features precise-cachegrind

Step 2 — profile a single rustc

# Cachegrind (instruction-level)
./target/release/collector profile_runtime <RUSTC> cachegrind --group std

# perf-record (sampling)
./target/release/collector profile_runtime <RUSTC> perf-record --group std

# Profile more iterations so the profiler gathers more data
./target/release/collector profile_runtime <RUSTC> cachegrind --group std --iterations 20

<RUSTC> is a path or +toolchain (same as bench_runtime_local). The --iterations value (default 5) controls how many times the profiled function runs so the profiler has enough samples.

Profiling always uses cached (in-place) compilation and forces debug info so filenames and line numbers are available in the generated profiles.

Step 3 — find the output

Profiles are written to results-runtime/:

  • Cachegrind: cgout-<rustc_id>-<group>-<bench>.<N> (raw) and cgann-<rustc_id>-<group>-<bench>.<N> (annotated).
  • perf-record: perf-<rustc_id>-<group>-<bench>.<N> (raw) and perfreport-<rustc_id>-<group>-<bench>.<N> (annotated).

The collector prints the path at the end:

Profiling complete, result can be found in `results-runtime/...`

Step 4 — diff two rustc builds (cachegrind only)

Compare a modified rustc against a baseline to see exactly where instructions changed:

./target/release/collector profile_runtime <rustc-modified> cachegrind --rustc2 <rustc-baseline> --group std

The collector profiles both toolchains, then runs cg_diff on each benchmark's pair of cachegrind outputs and annotates the diff. The diff is stored alongside the per-toolchain profiles, and the collector prints:

Diff stored in `results-runtime/...`

--rustc2 with perf-record is unsupported. Use cachegrind for diffs.

Step 5 — profile a single benchmark within a group

profile_runtime profiles every benchmark in the group. To narrow down, use the same filter flags as bench_runtime_local:

./target/release/collector profile_runtime <RUSTC> cachegrind --group std \
    --include std_bench::hash --iterations 10

(--include/--exclude/--exclude_suffix/--exact_match are accepted here too.)

Troubleshooting

  • valgrind is not installed — install valgrind (and cg_annotate/cg_diff, usually in the same package).
  • Empty profile — the benchmark may be too fast; raise --iterations.
  • --rustc2 perf-record errors — perf-record has no diff support; switch to cachegrind.
  • No line numbers in the annotation — profiling forces debug info; if you built a custom benchmark crate without debuginfo, rebuild it.

Next steps