Performance Basics
10 examples to get you started with Performance - 7 basic and 3 intermediate.
Prerequisites
- Rust 1.97.0 (edition 2024)
- Release builds for meaningful timings:
cargo build --release
Basic Examples
1. Measure Before Optimizing
Profile or benchmark before changing code.
cargo build --release
cargo bench # if Criterion benches exist
# or: perf record / samply for whole-process profiles- Debug builds are 10-100x slower than release
- Guessing hotspots wastes time
- Document baseline before changes
- Re-measure after each optimization
Related: Benchmarking & Profiling
2. Release Profile Defaults
Ship with optimized binaries.
[profile.release]
opt-level = 3
lto = "thin"cargo run --releaseopt-level = 3enables LLVM optimizations- LTO enables cross-crate inlining
- Profile dev vs release separately
- Never tune algorithms in debug only
Related: Release Tuning
3. Zero-Cost Abstractions
Iterators and generics compile to tight loops.
let sum: u64 = (0..1_000_000).map(|x| x as u64).sum();- High-level code can match hand-written loops
- Inspect assembly with
cargo asmor godbolt if unsure - Prefer iterators until profiling says otherwise
- Clarity first, micro-opt second
Related: Zero-Cost Abstractions
4. Avoid Unnecessary Allocations
Stack and reuse beat heap churn.
let mut buf = String::with_capacity(256);
buf.clear(); // reuse in loopwith_capacityprevents repeated realloc&stroverStringwhen borrowing suffices- Watch
clone()in hot loops cargo clippyflags redundant clones
Related: Understanding Allocations
5. Borrow Instead of Clone
Ownership copies are explicit and costly.
fn process(items: &[Item]) {
for item in items {
use_item(item);
}
}&Tand&[T]avoid deep copiesCow<'_, str>when rare mutation needed- Design APIs to take references
- Clone only at persistence boundaries
Related: Avoiding Clones
6. Use Appropriate Collections
Pick structures for access patterns.
use std::collections::HashMap;
let mut index: HashMap<u64, User> = HashMap::with_capacity(1024);Vecfor sequential accessHashMap/BTreeMapfor lookupsVecDequefor queue patterns- Pre-size with
with_capacitywhen size known
7. Inline Hints Sparingly
#[inline] helps cross-crate hot paths.
#[inline]
pub fn cheap_op(x: u32) -> u32 {
x.wrapping_mul(31)
}- Compiler inlines most small functions in LTO builds
#[inline]on public tiny helpers in libs- Do not sprinkle on everything
- Profile before adding inline attributes
Intermediate Examples
8. Cache-Friendly Data Layout
Order fields for locality.
#[repr(C)]
struct Particle {
x: f32,
y: f32,
active: bool,
}- SoA vs AoS for hot loops
repr(C)for predictable layout with FFI- See memory layout guide for ECS patterns
Related: Memory Layout & Cache
9. SIMD and Vectorization
Let LLVM vectorize or use explicit SIMD.
let sum: u32 = data.iter().copied().sum();- Simple iterator chains often auto-vectorize at
-O3 std::simdfor hand-tuned kernels- Benchmark before manual SIMD complexity
Related: SIMD & Vectorization
10. Binary Size vs Speed
Tune for deploy constraints.
[profile.release]
opt-level = "z"
strip = "symbols"- Embedded and CLI tools may favor size
- Servers often favor throughput
- Measure both latency and binary MB
Related: Reducing Binary Size
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.