Optimization Levels
The opt-level setting in Cargo.toml (or -C opt-level=<level> passed to rustc) controls which LLVM optimization passes are executed during compilation, trading off compilation time and binary size against runtime execution speed.
Compile Time & Binary Size vs. Runtime Speed:
`opt-level = 0` Fastest compile, zero optimization (Debug default)
`opt-level = 1` Basic register allocation & dead-code cleanup (Fast compile, ~3-5x speedup)
`opt-level = 2` Most speed optimizations without aggressive code bloat
`opt-level = 3` Maximum speed: aggressive loop unrolling, inlining, & SIMD (Release default)
`opt-level = "s"` Optimize for small binary size while keeping reasonable speed
`opt-level = "z"` Optimize for minimum binary size (disables loop vectorization/unrolling)
Detailed Comparison of All Six opt-level Settings
opt-level |
Equivalent Clang/GCC Flag | Loop Unrolling & Auto-Vectorization | Function Inlining Threshold | Primary Use Case |
|---|---|---|---|---|
0 |
-O0 |
Disabled | Only #[inline(always)] |
Instantaneous incremental cargo check / cargo build and interactive LLDB stepping. |
1 |
-O1 |
Disabled | Conservative | Fast test compilation when 0 is too slow for compute-heavy unit tests. |
2 |
-O2 |
Enabled (conservative) | Standard | Balanced production builds where instruction-cache footprint matters alongside speed. |
3 |
-O3 |
Aggressive | Aggressive | Default [profile.release] and [profile.bench] for maximum CPU throughput. |
"s" |
-Os |
Vectorization only if size-neutral | Reduced | WebAssembly (.wasm), mobile libraries, and flash-constrained embedded firmware. |
"z" |
-Oz |
Disabled | Minimal | Ultra-compact bootloaders, microcontrollers, and size-capped WASM binaries. |
Hybrid Per-Package Optimization in Cargo.toml
You do not have to choose a single opt-level for your entire dependency graph! Cargo's profile overrides ([profile.<name>.package.<pkg>]) allow you to compile hot third-party dependencies (such as image, regex, sqlx, or sha2) at opt-level = 2 or 3 even during normal cargo run and cargo test debug builds:
[profile.dev]
# Keep your own workspace crates at opt-level = 0 or 1 for sub-second rebuilds:
opt-level = 0
# Compile all external dependencies at opt-level = 2 (cached once in target/debug/deps!):
[profile.dev.package."*"]
opt-level = 2
# Compile a specific performance-critical workspace crate at opt-level = 3 even in dev:
[profile.dev.package.my_crypto_engine]
opt-level = 3
When opt-level = 2 or "s" Can Outperform opt-level = 3
While opt-level = 3 is fastest for tight numerical kernels and benchmarks that fit inside the CPU's 32 KiB / 64 KiB L1 instruction cache (L1i), aggressive inlining and loop unrolling across a massive 50 MB server binary can occasionally increase L1 instruction cache misses. Benchmarking your full application under both opt-level = 2 and opt-level = 3 verifies whether aggressive unrolling helps or hurts your specific workload.
pub fn polynomial_horner(coeffs: &[f64], x: f64) -> f64 {
// At opt-level >= 2, this fold lowers to a tight FMA (`fmadd`) register loop:
coeffs.iter().rev().fold(0.0, |acc, &c| acc.mul_add(x, c))
}
fn main() {
// Evaluate 2x^2 + 3x + 5 at x = 4.0 -> 2(16) + 12 + 5 = 49.0
let y = polynomial_horner(&[5.0, 3.0, 2.0], 4.0);
println!("p(4.0) = {y:.1}");
}
p(4.0) = 49.0