Rust perf
Rust performance optimization — memory, compiler hints, and profiling. Use when optimizing allocations (SmallVec, with_capacity, arena), configuring release profiles (LTO, PGO, codegen-units), adding inline hints, benchmarking with criterion, or profiling with flamegraph. Also use when reviewing code for unnecessary allocations, premature optimization, or format! in hot paths.From its SKILL.md
npx -y skills add dawidpereira/rust-skills --skill rust-perfAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.0 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
Performance Optimization
Core Question
Have you measured first, or are you guessing?
Intuition about performance is often wrong. The code you think is slow frequently isn't. Profile first, then optimize the actual bottleneck with data-driven decisions.
Quick Decisions
| Situation | Reach For | Why |
|---|---|---|
| Collection size known upfront | Vec::with_capacity(n) | Avoids ~10 reallocations for 1000 elements |
| Usually-small collection (2-8 items) | SmallVec<[T; N]> | Stack-allocated for common case, heap fallback |
| Hard upper bound, no heap allowed | ArrayVec<T, N> | Guaranteed stack-only, panics on overflow |
| Fixed-size heap data, never grows | Box<[T]> / Box<str> | Saves 8 bytes per instance vs Vec/String |
| Often-empty vectors in many instances | ThinVec<T> | 8 bytes empty vs 24 bytes for Vec |
| One enum variant much larger than others | Box the large variant | Clippy large_enum_variant catches this |
| Millions of short strings (< 24 chars) | CompactString | Inline storage, zero heap allocation |
| Repeatedly cloning into same variable | target.clone_from(&source) | Reuses existing allocation |
| Temporary collection in a loop | .clear() + reuse | Keeps capacity across iterations |
format!() in a hot loop | write!(&mut buf, ...) | Zero allocation with reused buffer |
| Many small allocations (AST, parsing) | bumpalo::Bump arena | Bump-pointer allocation, bulk free |
| Parsing without ownership needs | &str / &[u8] slices | Zero-copy, no allocation |
| Map insert-or-update | .entry().or_insert() | Single lookup instead of two |
| Iterating with manual indexing | .iter() / .zip() | Eliminates bounds checks, enables SIMD |
Intermediate .collect() calls | Chain iterators lazily | One allocation, one pass |
| Release build performance | lto = "fat", codegen-units = 1 | 10-20% improvement typical |
| Proven hot inner loop function | #[inline] or #[inline(always)] | Cross-crate inlining hint |
| Error construction path | #[cold] + #[inline(never)] | Keeps cold code out of hot path |
| Need to benchmark correctly | black_box() inputs and outputs | Prevents dead code elimination |
Optimization Priority
Optimize in this order — each level has roughly 10x less impact than the one above:
- Algorithm & data structure — O(n) vs O(n^2) dwarfs everything else
- Data layout — SoA vs AoS, cache-friendly access, avoid pointer chasing
- Allocations — with_capacity, reuse buffers, arena allocators
- Compiler hints — LTO, PGO, codegen-units, inline hints
- SIMD & low-level — portable SIMD, bounds check elimination, target-cpu
Profile BEFORE Optimizing
Tools
| Tool | What It Shows | When to Use |
|---|---|---|
cargo flamegraph | CPU time by call stack | First step — find where time goes |
cargo instruments -t time (macOS) | CPU time profiling | macOS alternative to perf |
criterion | Micro-benchmark with statistics | Compare before/after for specific functions |
DHAT / heaptrack | Heap allocation sites and counts | When allocation pressure is suspected |
perf stat -e cache-misses | Cache efficiency | When data layout matters |
cargo bloat | Binary size by function/crate | When binary size is a concern |
Workflow
1. Write correct code first
2. Write benchmarks for suspected hot paths
3. Profile under realistic load
4. Identify actual bottlenecks (top 10% of time)
5. Optimize ONE thing
6. Measure improvement with same benchmark
7. Repeat if needed
Criterion Quick Setup
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn bench_hot_function(c: &mut Criterion) {
let data = generate_test_data(1000);
c.bench_function("hot_function", |b| {
b.iter(|| hot_function(black_box(&data)))
});
}
criterion_group!(benches, bench_hot_function);
criterion_main!(benches);
Anti-Patterns to Watch For
| Anti-Pattern | Fix |
|---|---|
format!() in hot loop | write!(&mut buffer, ...) with reused buffer |
Intermediate .collect() between iterator steps | Chain lazily, collect once at end |
| Optimizing without profiling data | Run cargo flamegraph first |
#[inline(always)] everywhere | Let compiler decide; use #[inline] for cross-crate |
unsafe to skip bounds checks | Use iterators — they eliminate bounds checks safely |
contains_key() then insert() | Use .entry() API for single lookup |
Usage Scenarios
Scenario 1: "My web handler is slow and I'm not
sure why"
-> Run cargo flamegraph on a representative workload.
Look for wide bars (time hogs), malloc/free
(allocation heavy), memcpy (unnecessary copies).
Optimize only what the flamegraph shows as hot.
Scenario 2: "I have a Vec that I fill in a loop and
it's showing up in profiling"
-> Check if size is known: use with_capacity(). If the
Vec is reused across iterations: .clear() instead of
creating new. If always small (< 8 items): consider
SmallVec. If fixed after creation: convert to
Box<[T]>.
Scenario 3: "I need maximum throughput for a release
binary"
-> Set release profile: lto = "fat",
codegen-units = 1, panic = "abort",
strip = true. For deployment on known hardware:
RUSTFLAGS="-C target-cpu=native". For maximum gains:
use PGO with representative workloads.
Release Profile
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
[profile.bench]
inherits = "release"
debug = true
strip = false
[profile.dev.package."*"]
opt-level = 3
Reference Files
| File | Read When |
|---|---|
| references/memory.md | Allocation strategies: with_capacity, SmallVec, arena, zero-copy, compact strings, type size assertions |
| references/compiler.md | Compiler hints: inline, #[cold], LTO, PGO, codegen-units, target-cpu, SIMD, cache layout |
| references/runtime.md | Runtime patterns: iterators vs indexing, lazy chains, entry API, drain/extend, collect patterns, benchmarking |
Cross-References
| When | Check |
|---|---|
| Clone vs borrow decision for performance | rust-ownership -> Quick Decisions |
| Error construction on cold paths | rust-errors -> Quick Decisions |
| Async runtime and spawn_blocking for CPU work | rust-async -> Quick Decisions |
| API design that avoids unnecessary allocations | rust-api -> Quick Decisions |
| Clippy perf lints and lint configuration | rust-quality -> Quick Decisions |
What ships with it: 3 files
24.5 KB alongside SKILL.md
references/
- compiler.md8.2 KB
- memory.md7.6 KB
- runtime.md8.7 KB
Gives 0 of the 12 instructions most performance cost skills give in ~1.7k tokens
Counted across 803 of the 1,058 authors here whose files we hold, read 2026-08-07
- Keep skill files under 500 lines or tokensin 82 of 803, across 16 files
- Use imperative form in instructionsin 80 of 803, across 9 files
- Draft assertions while test runs are in progressin 75 of 803, across 9 files
- Create two to three realistic test promptsin 74 of 803, across 9 files
- Write skill descriptions to be pushyin 72 of 803, across 7 files
- Save test cases to evals JSONin 72 of 803, across 6 files
- Ask questions about edge cases and input formatsin 72 of 803, across 7 files
- Save timing data immediately when runs completein 70 of 803, across 5 files
- Include all trigger conditions in the skill descriptionin 69 of 803, across 3 files
- Launch all test runs in a single turn or simultaneouslyin 69 of 803, across 3 files
- Capture intent before writing a skillin 67 of 803, across 1 file
- Import directly instead of barrel filesin 52 of 803, across 15 files
Said here and by no other author read
- Write benchmarks for hot paths
- Measure improvements with the same benchmark
- Preallocate collections if size is known
- Chain iterators lazily to avoid intermediate collections
- Set LTO and single codegen unit for release builds
- Use black box for benchmark inputs and outputs
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.