Rust Performance Engineering

Profile-Guided Optimization

Profile-Guided Optimization (PGO) is a two-phase compilation technique where the compiler uses real runtime execution profiles recorded from representative workloads to optimize branch prediction layouts, function inlining thresholds, virtual call speculative devirtualization, and instruction cache placement—typically yielding an additional 5% to 20% throughput improvement on top of -C opt-level=3 and LTO!

Profile-Guided Optimization (PGO) 4-Step Workflow:

  Step 1: Compile Instrumented Binary (`-C profile-generate=/tmp/pgo-data`)
                     |
                     v
  Step 2: Run Binary on Representative Workload (Writes `.profraw` counters)
                     |
                     v
  Step 3: Merge Profiles (`llvm-profdata merge -o merged.profdata /tmp/pgo-data`)
                     |
                     v
  Step 4: Recompile with Profile (`-C profile-use=merged.profdata`)
          ---> Produces PGO-Optimized Production Binary!

What LLVM Optimizes Using PGO Data

Without runtime data, a static compiler must guess which branch of an if/match is taken 99% of the time and which functions are hottest at runtime. With PGO .profdata counters, LLVM performs four targeted optimizations:

Optimization Static Compiler (opt-level = 3) With PGO (-C profile-use=...)
Basic Block Placement Guesses branch probabilities heuristically. Places the 99%-taken hot branch inline as the fall-through path (zero branch-taken penalty) and moves cold blocks to the end of the function!
Function Splitting & Layout Places functions in arbitrary source/CGU order. Packs frequently called functions adjacently on the same 4 KiB / 2 MiB OS code pages, slashing L1 instruction cache (L1i) and iTLB misses!
Inlining Budget Uses fixed AST/IR size heuristics. Aggressively inlines call sites proven to execute millions of times, while refusing to inline cold call sites.
Indirect Call Promotion Emits an indirect vtable/fn-pointer jump. If &dyn Trait resolves to ConcreteTypeA 98% of the time in the profile, emits if vtable == A { inlined_A() } else { vtable_call() }!

Running PGO on a Rust Project

Manual 4-Step Commands (via rustup component add llvm-tools-preview):

# 1. Build the instrumented release binary
RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" cargo build --release

# 2. Execute the binary against realistic benchmark/production traffic
./target/release/my_app --benchmark-workload

# 3. Merge the generated `.profraw` files into a single `.profdata` file
$(rustc --print target-libdir)/../bin/llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data

# 4. Rebuild the final binary guided by the recorded profile
RUSTFLAGS="-Cprofile-use=/tmp/pgo-data/merged.profdata -Cllvm-args=-pgo-warn-missing-function" cargo build --release

Automated Workflow with cargo-pgo

The ecosystem subcommand cargo-pgo (cargo install cargo-pgo --locked) automates all four steps—including directory cleanup and llvm-profdata invocation—into two simple commands:

# 1. Build instrumented binary and run workload/benchmarks to collect profile
cargo pgo bench

# 2. Build final PGO-optimized release binary
cargo pgo optimize build
pub fn classify_opcode(op: u8) -> u32 {
    // Under PGO, if `0x01` (NOP/FastData) accounts for 98% of real traffic,
    // LLVM places arm `0x01` as the first fall-through comparison!
    match op {
        0x00 => 10,
        0x01 => 1,
        0x02 => 25,
        _ => 100,
    }
}

fn main() {
    let stream = [0x01u8, 0x01, 0x01, 0x02, 0x01];
    let cost: u32 = stream.iter().map(|&op| classify_opcode(op)).sum();
    println!("Total opcode stream cost: {cost}");
}
Total opcode stream cost: 29