Rust Performance Engineering

Link-Time Optimization

In a standard build, rustc compiles each crate (and each parallel codegen unit within a crate) independently: when compiling the final binary, the system linker simply stitches pre-compiled machine-code object files together. Link-Time Optimization (LTO) embeds LLVM bitcode into intermediate artifacts and delays final optimization until link time, allowing LLVM to inline functions, propagate constants, devirtualize calls, and eliminate dead code across the entire dependency graph.

Standard Compilation (`lto = false`, `codegen-units = 16`):
  [ Crate A (16 CGUs) ] ---> Machine Object `.o` --+
  [ Crate B (16 CGUs) ] ---> Machine Object `.o` --+--> [ Native Linker ] ---> Binary
  (No optimization across non-`#[inline]` crate boundaries or across CGUs!)

Link-Time Optimization (`lto = "thin"` or `lto = "fat"`):
  [ Crate A ] ---> LLVM Bitcode `.bc` --+
  [ Crate B ] ---> LLVM Bitcode `.bc` --+--> [ Whole-Program LLVM Optimizer ] ---> Binary
  (Inlines & eliminates dead code across ALL crates in the dependency tree!)

Comparing Cargo LTO Modes and codegen-units

Setting in [profile.release] Link Time Runtime Speedup Binary Size Reduction How It Works
lto = false (default) Fastest Baseline Baseline Optimizes only within each individual Codegen Unit (CGU).
lto = "off" Fastest None None Disables even thin intra-crate LTO across CGUs.
lto = "thin" (Recommended) Fast (Parallel) ~5%–15% faster ~5%–15% smaller Exchanges compact function summaries across all crates and runs cross-crate inlining and dead-code elimination in parallel across all CPU cores!
lto = "fat" (or lto = true) Slow (Single-threaded bottleneck) ~5%–20% faster ~10%–25% smaller Merges all LLVM bitcode from all crates into a single monolithic module for whole-program optimization.

Combining lto with codegen-units = 1

By default, [profile.release] sets codegen-units = 16 so each crate is split into 16 parallel chunks for faster compilation. However, splitting a crate into 16 chunks prevents LLVM from optimizing across functions placed in different chunks of the same crate unless LTO is enabled.

For production release binaries, pairing lto = "thin" (or lto = true) with codegen-units = 1 produces the fastest and smallest executable:

# File: Cargo.toml
[profile.release]
opt-level = 3
lto = "thin"          # Or `lto = true` ("fat") for maximum whole-program optimization
codegen-units = 1     # Compile each crate as a single unified LLVM module
strip = "symbols"
pub fn compute_hash_step(state: u64, byte: u8) -> u64 {
    state.rotate_left(5) ^ u64::from(byte).wrapping_mul(0x9E37_79B9_7F4A_7C15)
}

pub fn hash_slice(bytes: &[u8]) -> u64 {
    bytes.iter().fold(0u64, |acc, &b| compute_hash_step(acc, b))
}

fn main() {
    println!("Hash of 'lto-demo': {:#018x}", hash_slice(b"lto-demo"));
}
Hash of 'lto-demo': 0x4b10cf63a5f6f3b0

Faster Linking with Modern Native Linkers (lld and mold)

When building large Rust binaries (or running cargo test with dozens of test executables), the system linker (ld) can take several seconds per build. Configuring lld (LLVM's linker) or mold in .cargo/config.toml accelerates final linking by 3x–10x and supports ThinLTO natively.