rust-performance
Skill1 starsChanged 6 months agoMITView on GitHub
Rust performance optimization, profiling, and benchmarking skill. ALWAYS use this skill when: - User says Rust code is "slow", "takes too long", or needs to be "faster" - User wants to profile CPU usage, memory allocations, or find bottlenecks - User asks about flamegraph, perf, criterion, DHAT, heaptrack, or any profiling tool - User is concerned about allocations, cloning, or memory efficiency - User asks about Vec::with_capacity, Cow, SmallVec, or allocation strategies - User wants to write benchmarks or compare performance of implementations - User asks about release mode, LTO, codegen-units, or compiler optimizations - User mentions "too many allocations", "memory usage", or "throughput" - User works with large datasets, parsing, or performance-critical code Even for general "make this faster" requests on Rust code, use this skill.
- Installs packages
7 other projects use a version of this.
Adopted by
Most recently taken up by
- Lu1sDV/skillsmdtheir own version, first seen Nov 4, 2026
- po4yka/rust-skillstheir own version, first seen Nov 2, 2026
- full-stack-skills/rust-skillstheir own version, first seen Oct 15, 2026
- Dankosik/rust-cli-skillstheir own version, first seen Sep 8, 2026
- jscott3201/yet-more-skillstheir own version, first seen Sep 7, 2026
- botirkhaltaev/botir-skillstheir own version, first seen Jul 24, 2026
Used with
Projects with it also use rust-unsafe-ffi and rust-macros.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
botirkhaltaevSkillrust-unsafe-ffi
Master unsafe Rust, FFI, and foreign function interface programming. Use when writing unsafe Rust code with raw pointers, interfacing with C/C++ libraries, creating bindings with bindgen, implementing zero-cost abstractions, working with uninitialized memory, or understanding the safety/unsafety boundary.
Also in 3 projects that use this
dawidpereiraSkillrust-macros
Rust macros — macro_rules!, proc macros, derive macros, attribute macros, fragment specifiers, repetition, hygiene, cargo expand, when to use macros vs generics. Use when writing or debugging declarative or procedural macros, choosing between macros and other abstractions, understanding expansion errors, or generating repetitive code.
Also in 4 projects that use this
Other versions7 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 7
- huiali/rust-skills★ 47same name
- full-stack-skills/rust-skills★ 8same name
- po4yka/rust-skills★ 5same name
- botirkhaltaev/botir-skills★ 4same name
- jscott3201/yet-more-skills★ 4same name
- Lu1sDV/skillsmd★ 2same name
- Dankosik/rust-cli-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 2 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 31
Benchmark changes: - Verify improvements
- Distinctive: line 36
Always benchmark in release mode with optimizations:
- Very specific: line 193
Use black_box() to prevent optimization
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rust Performance Optimization
Comprehensive guide to profiling, measuring, and optimizing Rust code. Based on "The Rust Performance Book" and real-world optimization patterns.
Quick Navigation
- references/profiling.md - Profiling tools and techniques
- references/allocations.md - Reducing heap allocations
- references/concurrency.md - Parallel and async optimization
- references/compiler_optimizations.md - Release profiles, LTO, PGO, CPU targets
Golden Rules
- Measure first - Profile before optimizing
- Optimize hot paths - 90% of time in 10% of code
- Benchmark changes - Verify improvements
- Consider tradeoffs - Speed vs memory vs complexity
Release Mode
Always benchmark in release mode with optimizations:
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
# For profiling (symbols but optimized)
[profile.release-with-debug]
inherits = "release"
debug = truecargo build --release
cargo run --releaseQuick Performance Wins
1. Use &str Instead of String
// Allocates on every call
fn greet_slow(name: String) {
println!("Hello, {}!", name);
}
// Zero allocation
fn greet_fast(name: &str) {
println!("Hello, {}!", name);
}2. Pre-allocate Collections
// Reallocates as it grows
let mut v = Vec::new();
for i in 0..1000 {
v.push(i);
}
// Single allocation
let mut v = Vec::with_capacity(1000);
for i in 0..1000 {
v.push(i);
}
// Even better: use iterators
let v: Vec<_> = (0..1000).collect();3. Avoid Unnecessary Clones
// Bad: clones unnecessarily
fn process(items: Vec<Item>) -> Vec<Result> {
items.iter()
.map(|item| item.clone()) // Don't clone if you don't need it
.map(|item| transform(item))
.collect()
}
// Good: take ownership or borrow
fn process(items: Vec<Item>) -> Vec<Result> {
items.into_iter() // Consume the Vec
.map(transform)
.collect()
}4. Use Cow<str> for Maybe-Owned Strings
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input) // No allocation
}
}5. Use collect() Strategically
// Creates intermediate Vec
let sum: i32 = items.iter()
.map(|x| x * 2)
.collect::<Vec<_>>() // Unnecessary
.iter()
.sum();
// Direct iteration
let sum: i32 = items.iter()
.map(|x| x * 2)
.sum();Benchmarking
Criterion.rs
The gold standard for Rust benchmarks:
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "my_benchmark"
harness = false// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| {
b.iter(|| fibonacci(black_box(20)))
});
// Compare implementations
let mut group = c.benchmark_group("String Ops");
group.bench_function("clone", |b| {
b.iter(|| String::from("hello").clone())
});
group.bench_function("to_string", |b| {
b.iter(|| "hello".to_string())
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);cargo bench
cargo bench -- "fib" # Run specific benchmarksKey Points
- Use
black_box()to prevent optimization - Multiple iterations for statistical significance
- Compare baseline vs optimized
- Watch for outliers
Profiling
CPU Profiling
perf (Linux):
perf record -g --call-graph dwarf target/release/myapp
perf reportflamegraph:
cargo install flamegraph
cargo flamegraph --bin myapp
# Open flamegraph.svg in browsersamply (Cross-platform):
cargo install samply
samply record target/release/myappMemory Profiling
DHAT (Heap profiling):
[dependencies]
dhat = "0.3"#[cfg(feature = "dhat")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
fn main() {
#[cfg(feature = "dhat")]
let _profiler = dhat::Profiler::new_heap();
// Your code here
}Heaptrack (Linux):
heaptrack target/release/myapp
heaptrack --analyze heaptrack.myapp.*.zstFinding Allocations
// Track allocations with a custom allocator
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::SeqCst);
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
ALLOCATED.fetch_sub(layout.size(), Ordering::SeqCst);
System.dealloc(ptr, layout)
}
}
#[global_allocator]
static A: CountingAlloc = CountingAlloc;
fn main() {
let before = ALLOCATED.load(Ordering::SeqCst);
// Code to measure
let after = ALLOCATED.load(Ordering::SeqCst);
println!("Allocated {} bytes", after - before);
}Common Optimizations
String Operations
// Slow: Many allocations
let mut s = String::new();
for i in 0..100 {
s = s + &i.to_string(); // New allocation each time
}
// Better: Single buffer
let mut s = String::with_capacity(300);
for i in 0..100 {
use std::fmt::Write;
write!(s, "{}", i).unwrap();
}
// Best: Join iterator
let s: String = (0..100).map(|i| i.to_string()).collect();Hash Maps
use std::collections::HashMap;
// Pre-size when you know the count
let mut map = HashMap::with_capacity(1000);
// Use entry API (single lookup)
*map.entry(key).or_insert(0) += 1;
// Consider FxHashMap for integer keys
use rustc_hash::FxHashMap;
let mut map: FxHashMap<u64, Value> = FxHashMap::default();Iterators vs Loops
// Iterators often optimize better
let sum: i32 = data.iter().map(|x| x * 2).filter(|x| *x > 10).sum();
// But sometimes explicit loops are clearer and equally fast
let mut sum = 0;
for x in &data {
let doubled = x * 2;
if doubled > 10 {
sum += doubled;
}
}Avoid Bounds Checking
// Bounds check on each access
for i in 0..v.len() {
process(v[i]);
}
// No bounds checks (iterator)
for item in &v {
process(*item);
}
// Unsafe: skip bounds check (only when proven safe!)
unsafe {
for i in 0..v.len() {
process(*v.get_unchecked(i));
}
}Stack vs Heap
// Heap allocated (Box)
let data = Box::new([0u8; 1024]);
// Stack allocated (if small enough)
let data = [0u8; 1024];
// Use arrays for fixed-size, known at compile time
// Use Vec for dynamic sizeAsync Performance
// Avoid holding locks across awaits
let data = {
let guard = mutex.lock().await;
guard.clone() // Clone and release lock
}; // Lock released here
process(data).await; // Await without lock
// Use tokio::spawn for CPU-bound work
let result = tokio::task::spawn_blocking(|| {
expensive_computation()
}).await?;
// Buffer I/O
use tokio::io::BufReader;
let reader = BufReader::new(file);Data Structure Choice
| Use Case | Data Structure | Why |
| Sequential access | Vec<T> | Cache-friendly |
| Key-value lookup | HashMap / FxHashMap | O(1) average |
| Sorted + lookup | BTreeMap | O(log n), ordered |
| Unique elements | HashSet | O(1) contains |
| FIFO queue | VecDeque | O(1) push/pop both ends |
| Small fixed set | ArrayVec / SmallVec | No heap allocation |
| Bit flags | bitflags | Memory efficient |
Compiler Hints
// Likely/unlikely branches (nightly)
#![feature(core_intrinsics)]
use std::intrinsics::{likely, unlikely};
if unlikely(error_condition) {
handle_error();
}
// Inline hints
#[inline] // Suggest inlining
#[inline(always)] // Force inlining
#[inline(never)] // Prevent inlining
#[cold] // Rarely called (optimize for space)
fn rarely_used() { ... }
// Target-specific optimization
#[cfg(target_feature = "avx2")]
fn simd_process(data: &[f32]) { ... }Arena Allocators
For batch allocations freed together (parsers, request processing):
use bumpalo::Bump;
// All allocations from one arena — freed together when arena drops
fn parse<'a>(input: &str, arena: &'a Bump) -> Vec<&'a Node> {
tokenize(input).map(|t| arena.alloc(Node::new(t))).collect()
}
// Per-request: allocate freely, free everything at once
async fn handle(req: Request) -> Response {
let arena = Bump::new();
let parsed = parse(&req.body, &arena);
generate_response(parsed)
} // arena dropped: all parsed nodes freed in one deallocSmallVec — Inline Small Collections
use smallvec::SmallVec;
// Up to 4 items stored inline (stack), spills to heap beyond that
let mut tags: SmallVec<[&str; 4]> = SmallVec::new();
tags.push("performance");
tags.push("rust");
// No heap allocation for ≤ 4 itemsWrite! Over format!
use std::fmt::Write;
// Bad: allocates a new String every time
let s = format!("key={} val={}", key, val);
// Good: write into pre-allocated buffer, clear and reuse
let mut buf = String::with_capacity(128);
for (key, val) in &map {
buf.clear();
write!(buf, "key={key} val={val}").unwrap();
send(&buf);
}Entry API for HashMap
// Bad: two lookups
if let Some(v) = map.get_mut(&k) { *v += 1; } else { map.insert(k, 1); }
// Good: single lookup
*map.entry(k).or_insert(0) += 1;Struct Size — Keep It Small
use std::mem::size_of;
// Catch size regressions at compile time
const _: () = assert!(size_of::<MyEvent>() <= 64);
// Large enum variant → box it
enum Message {
Ping,
Text(String),
// HugeThing(VeryLargeStruct), // Makes ALL variants huge!
HugeThing(Box<VeryLargeStruct>), // Pointer-sized, heap only when needed
}Advanced Compiler Optimizations
1. Compile with CPU Native Target
In release mode, use target-cpu=native to allow the compiler to generate machine instructions specifically optimized for the host CPU (enabling AVX, SSE4, etc.).
RUSTFLAGS="-C target-cpu=native" cargo build --release2. Instruct the Compiler on Cold Paths
Mark rarely executed logic (like initialization, configuration loading, or panic routes) with the #[cold] attribute. This instructs the compiler to optimize the layout of code instructions to prioritize hot execution flows.
#[cold]
fn parse_dev_configurations() {
// rarely called; keeps instruction caches clean
}3. Explicit SIMD Vectorization
For high-performance numerical routines, consider using std::simd to process multiple elements in parallel.
#![feature(portable_simd)]
use std::simd::f32x4;
pub fn add_vectors(a: &[f32; 4], b: &[f32; 4]) -> [f32; 4] {
let sa = f32x4::from_slice(a);
let sb = f32x4::from_slice(b);
let sum = sa + sb;
let mut out = [0.0; 4];
sum.copy_to_slice(&mut out);
out
}Quick Checklist
- Profiled to find actual bottleneck?
- Running in release mode?
- Using
&strinstead ofStringwhere possible? - Pre-allocating collections?
- Avoiding unnecessary clones?
- Using iterators over index loops?
- Using appropriate data structures?
- Benchmarked before and after?
- No intermediate
.collect()in hot paths? - Using entry API for HashMap inserts?
- Considered SmallVec or arena for short-lived allocations?
References
- The Rust Performance Book
- bumpalo — arena allocator
- smallvec
- cargo-flamegraph
- samply — cross-platform profiler
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

