
Rust Profiling
- 335 installs
- 155 repo stars
- Updated June 27, 2026
- mohitmishra786/low-level-dev-skills
Profile hot Rust services and CLIs with cargo-flamegraph, perf, and tracing to find allocation churn, lock contention, and regressions before release.
About
Teaches Rust performance investigation using sampling profilers, flamegraphs, tracing instrumentation, and micro-benchmarks. Helps agents interpret hot paths in async and sync code, compare release builds with debug info, and recommend targeted fixes for lock contention, excessive cloning, and allocator pressure before shipping.
- cargo-flamegraph and perf record workflows
- Tracing spans for async hotspots
- DHAT and heap profiling for allocations
- Benchmark-driven before/after comparisons
- Release profile and LTO tuning guidance
Rust Profiling by the numbers
- 335 all-time installs (skills.sh)
- +24 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #120 of 596 Debugging skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill rust-profilingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 335 |
|---|---|
| repo stars | ★ 155 |
| Last updated | June 27, 2026 |
| Repository | mohitmishra786/low-level-dev-skills ↗ |
What it does
Profile hot Rust services and CLIs with cargo-flamegraph, perf, and tracing to find allocation churn, lock contention, and regressions before release.
Files
Rust Profiling
Purpose
Guide agents through Rust performance profiling: flamegraphs via cargo-flamegraph, binary size analysis, monomorphization bloat measurement, Criterion microbenchmarks, and interpreting profiling results with inlined Rust frames.
Triggers
- "How do I generate a flamegraph for a Rust program?"
- "My Rust binary is huge — how do I find what's causing it?"
- "How do I write Criterion benchmarks?"
- "How do I measure monomorphization bloat?"
- "Rust performance is worse than expected — how do I profile it?"
- "How do I use perf with Rust?"
Workflow
1. Build for profiling
# Release with debug symbols (needed for readable profiles)
# Cargo.toml:
[profile.release-with-debug]
inherits = "release"
debug = true
cargo build --profile release-with-debug
# Or quick: release + debug info inline
CARGO_PROFILE_RELEASE_DEBUG=true cargo build --release2. Flamegraphs with cargo-flamegraph
# Install
cargo install flamegraph
# Linux: uses perf (requires perf_event_paranoid ≤ 1)
sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'
cargo flamegraph --bin myapp -- arg1 arg2
# macOS: uses DTrace (requires sudo)
sudo cargo flamegraph --bin myapp -- arg1 arg2
# Profile tests
cargo flamegraph --test mytest -- test_filter
# Profile benchmarks
cargo flamegraph --bench mybench -- --bench
# Output
# Generates flamegraph.svg in current directory
# Open in browser: firefox flamegraph.svgCustom flamegraph options:
# More samples
cargo flamegraph --freq 1000 --bin myapp
# Filter to specific threads
cargo flamegraph --bin myapp -- args 2>/dev/null
# Using perf directly for more control
perf record -g -F 999 ./target/release-with-debug/myapp args
perf script | stackcollapse-perf.pl | flamegraph.pl > out.svg3. Binary size analysis with cargo-bloat
# Install
cargo install cargo-bloat
# Show top functions by size
cargo bloat --release -n 20
# Show per-crate size breakdown
cargo bloat --release --crates
# Include only specific crate
cargo bloat --release --filter myapp
# Compare before/after a change
cargo bloat --release --crates > before.txt
# make changes
cargo bloat --release --crates > after.txt
diff before.txt after.txtTypical output:
File .text Size Crate Name
2.4% 3.0% 47.0KiB std <std macros>
1.8% 2.3% 35.5KiB myapp myapp::heavy_module::process
1.2% 1.5% 23.1KiB serde serde::de::...4. Monomorphization bloat with cargo-llvm-lines
# Install
cargo install cargo-llvm-lines
# Show LLVM IR line counts (proxy for monomorphization)
cargo llvm-lines --release | head -40
# Filter to your crate only
cargo llvm-lines --release | grep '^myapp'Typical output:
Lines Copies Function name
85330 1 [LLVM passes]
7761 92 core::fmt::write
4672 11 myapp::process::<impl MyTrait for T>
3201 47 <alloc::vec::Vec<T> as core::ops::Drop>::dropHigh Copies count = monomorphization expansion. Fix:
// Before: generic, gets monomorphized for every T
fn process<T: AsRef<[u8]>>(data: T) -> usize {
do_work(data.as_ref())
}
// After: thin generic wrapper + concrete inner
fn process<T: AsRef<[u8]>>(data: T) -> usize {
fn inner(data: &[u8]) -> usize { do_work(data) }
inner(data.as_ref())
}5. Criterion microbenchmarks
# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_bench"
harness = false// benches/my_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn bench_process(c: &mut Criterion) {
// Simple benchmark
c.bench_function("process 1000 items", |b| {
let data: Vec<i32> = (0..1000).collect();
b.iter(|| process(black_box(&data))) // black_box prevents optimization
});
}
fn bench_sizes(c: &mut Criterion) {
let mut group = c.benchmark_group("process_sizes");
for size in [100, 1000, 10000].iter() {
let data: Vec<i32> = (0..*size).collect();
group.bench_with_input(
BenchmarkId::from_parameter(size),
&data,
|b, data| b.iter(|| process(black_box(data))),
);
}
group.finish();
}
criterion_group!(benches, bench_process, bench_sizes);
criterion_main!(benches);# Run all benchmarks
cargo bench
# Run specific benchmark
cargo bench --bench my_bench
# Run with filter
cargo bench -- process_sizes
# Compare with baseline (save/load)
cargo bench -- --save-baseline before
# make changes
cargo bench -- --baseline before
# View HTML report
open target/criterion/report/index.html6. perf with Rust (Linux)
# Record
perf record -g ./target/release-with-debug/myapp args
perf record -g -F 999 ./target/release-with-debug/myapp args # higher freq
# Report
perf report # interactive TUI
perf report --stdio --no-call-graph | head -40 # text
# Annotate specific function
perf annotate myapp::hot_function
# stat (quick counters)
perf stat ./target/release/myapp argsRust-specific perf tips:
- Build with
debug = 1(line tables only) for faster builds with line-level attribution - Use
RUSTFLAGS="-C force-frame-pointers=yes"for better call graphs without DWARF unwinding - Disable ASLR for reproducible addresses:
setarch $(uname -m) -R ./myapp
7. heaptrack / DHAT for allocations
# heaptrack (Linux)
heaptrack ./target/release/myapp args
heaptrack_print heaptrack.myapp.*.zst | head -50
# DHAT via Valgrind
valgrind --tool=dhat ./target/debug/myapp args
# Open dhat-out.* with dh_view.htmlFor flamegraph setup and Criterion configuration, see references/cargo-flamegraph-setup.md.
Related skills
- Use
skills/rust/rustc-basicsfor build configuration (debug symbols, profiles) - Use
skills/profilers/linux-perffor perf fundamentals - Use
skills/profilers/flamegraphsfor reading and interpreting flamegraph SVGs - Use
skills/profilers/valgrindfor allocation profiling with massif/DHAT
cargo-flamegraph Setup and Criterion Reference
cargo-flamegraph Setup
Linux Prerequisites
# Install perf
sudo apt-get install linux-tools-common linux-tools-$(uname -r) # Debian/Ubuntu
sudo dnf install perf # Fedora
sudo pacman -S perf # Arch
# Allow perf for current user (choose one)
sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid' # Temp
echo 'kernel.perf_event_paranoid = 1' | sudo tee -a /etc/sysctl.d/perf.conf # Permanent
sudo sysctl -p /etc/sysctl.d/perf.conf
# Allow kernel symbols
sudo sh -c 'echo 0 > /proc/sys/kernel/kptr_restrict'macOS Prerequisites
DTrace is used on macOS. Requires full disk access and SIP considerations:
# Check DTrace works
sudo dtrace -n 'BEGIN { exit(0); }'
# If SIP-restricted, boot into recovery and:
# csrutil enable --without dtraceInstallation
cargo install flamegraph
# Dependencies for flamegraph script
# Linux: inferno (default, pure Rust)
# Or install Brendan Gregg's scripts:
git clone https://github.com/brendangregg/FlameGraph
export PATH="$PATH:/path/to/FlameGraph"Usage Patterns
# Profile binary with args
cargo flamegraph --bin myapp -- --workers 4 --input data.bin
# Profile specific test
cargo flamegraph --test integration_tests -- test_name
# Profile benchmark (compare with criterion)
cargo flamegraph --bench my_bench -- --bench benchmark_name
# Profile example
cargo flamegraph --example my_example
# Custom frequency (samples/sec, higher = more accurate, more overhead)
cargo flamegraph --freq 997 --bin myapp # 997 Hz avoids aliasing
# Output to specific file
cargo flamegraph -o profile.svg --bin myapp
# Open in browser automatically
cargo flamegraph -o /tmp/fg.svg --bin myapp && xdg-open /tmp/fg.svgReading Flamegraphs
Wide frames = more CPU time
Tall stacks = deep call chains
Plateau tops = actual CPU time spent there
x-axis: NOT time, it's alphabetical within each stack level
y-axis: call stack depth (bottom = first called)Look for:
- Wide frames near the top (hot leaves — where CPU actually spends time)
- Unexpected std/alloc frames (excessive allocation)
- Many thin
<closure>frames (closure overhead in tight loops)
Criterion Reference
Benchmark Structure
use criterion::{
black_box, criterion_group, criterion_main,
Criterion, BenchmarkId, Throughput,
};
use std::time::Duration;
fn bench_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("throughput");
// Set measurement time and sample count
group.measurement_time(Duration::from_secs(10));
group.sample_size(100);
for size in [1024usize, 4096, 65536] {
let data = vec![0u8; size];
// Report throughput in bytes/sec
group.throughput(Throughput::Bytes(size as u64));
group.bench_with_input(
BenchmarkId::from_parameter(size),
&data,
|b, data| b.iter(|| process(black_box(data))),
);
}
group.finish();
}Statistical Configuration
fn configure(c: &mut Criterion) -> &mut Criterion {
c.measurement_time(Duration::from_secs(10)) // How long to measure
.sample_size(200) // Number of iterations to sample
.warm_up_time(Duration::from_secs(3)) // Warm-up before measurement
.noise_threshold(0.05) // 5% noise threshold
.significance_level(0.05) // p-value threshold
.confidence_level(0.95) // Confidence interval width
}Custom Measurement (wall vs CPU time)
use criterion::measurement::WallTime;
// Default is WallTime. For CPU-intensive without I/O, it's usually fine.
// For async benchmarks, use tokio's runtime:
fn bench_async(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
c.bench_function("async_op", |b| {
b.to_async(&rt).iter(|| async_operation())
});
}Comparing Results
# Save baseline
cargo bench -- --save-baseline main-branch
# Switch branch and compare
git checkout my-feature
cargo bench -- --baseline main-branchOutput shows:
process/1024 time: [12.345 µs 12.456 µs 12.567 µs]
change: [-5.2312% -4.8956% -4.5600%] (p = 0.00 < 0.05)
Performance has improved.Criterion with Async (Tokio)
[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
tokio = { version = "1", features = ["full"] }use criterion::async_executor::TokioExecutor;
fn bench_async(c: &mut Criterion) {
c.bench_function("async_fn", |b| {
b.to_async(TokioExecutor).iter(|| async {
async_fn(black_box(42)).await
})
});
}