What is Profile-Guided Optimization (PGO)?
Profile-Guided Optimization (PGO) is a technique that improves program performance by analyzing how it actually runs.
Instead of making general assumptions, the compiler collects real-world data about the program (like which branches are taken most often), and uses that data to guide optimizations such as :
- Function inlining
- Code layout for better cache locality
- Register allocation
How does Rust implement PGO?
Rust’s compiler, rustc, uses LLVM for PGO.
LLVM supports multiple approaches, e.g.,
- Sampling-based PGO (via
perf) - GCOV-style profiling (code coverage-based)
- IR-level instrumentation (LLVM adds profiling hooks itself)
Rust only supports IR-level instrumentation, where LLVM inserts profiling instructions directly into the Intermediate Representation (IR) during compilation.
PGO in Rust works in two stages :
- Compile-time instrumentation : Build a special binary with profiling hooks
- Runtime instrumentation : Run the instrumented binary to collect
.profrawfiles
Over workflow
- Compile the program with intrumentation enabled. E.g.,
rust -C profile-generate main.rs - Run the intrumented program (e.g,
./main) which generates adefault-<id>.profrawfile - Convert the
.profrawfile into a.profdatafile using LLVM’sllvm-profdatatool - Compile the program again, this time making use of the profiling data. E.g.,
rust -C profile-use=merged.profdata main`
Compile-time
When we talked earlier, we ran rustc -C profile-generate main.rs, which tells LLVM to insert profiling hooks into the generated binary.
rustc instructs LLMV to do so by setting flags :
// `PMBR` is an `LLVMPassManagerBuilderRef`
unwrap(PMBR)->EnablePGOInstrGen = true;
// Instrumented binaries have a default output path for the `.profraw` file
// hard-coded into them:
unwrap(PMBR)->PGOInstrGen = PGOGenPath;When compiling binaries with optimizations that utilizing profiling data, rustc again delegates most of the work to LLVM.
Essentially, it informs LLVM where the profiling data is located :
unwrap(PMBR)->PGOInstrUse = PGOUsePath;Runtime
Instrumentation-based PGO isn’t jsut about how we compile code, it also requires runtime support. After all, once we’ve built an instrumented binary, we need to run it to gather real-world profiling data.
In LLVM, the runtime components responsible for collection profile data are part of a seperate project called compiler-rt. These components are statically linked into every instrumented binary and take care of :
- Recording branch hits, function calls, and counters
- Writing the
.profrawfiles to disk
Rust doesn’t reinvent this wheel. Instead, it reuses LLVM’s runtime by bundling its C code into a Rust crate called profiler_builtins. This crate wraps the profiling logic from compile-rt and makes it available in Rust builds.
To make this work, you must explicitly enable it during compiler builds by bootstrap.toml settings :
profiler = true