Rust Performance

SkillDocs & knowledge

Profile and optimize Rust CPU, memory, I/O, async, and build performance with representative benchmarks.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Rust Performance skill

What this skill tells your AI

The instructions your AI receives, as published by caarlos0/dotfiles in skills/rust-performance/SKILL.md and read by ahel’s review.

Profile first, then change one measured bottleneck. Report the workload, tool, target, toolchain, build profile, and before/after values.

Measure Rust, not debug builds

Use optimized code with symbols:

[profile.profiling]
inherits = "release"
debug = "line-tables-only"

Use Criterion for microbenchmarks and std::hint::black_box only where the compiler could fold inputs or remove results. For CPU profiles use the repository's profiler, samply, perf, Instruments, or cargo flamegraph. Use DHAT or an allocation profiler when CPU samples point to allocation.

Allocations and ownership

  • Pre-size known growth with Vec::with_capacity, String::with_capacity, and HashMap::with_capacity.
  • Use clone_from when replacing an existing value can reuse its allocation.
  • Use Cow at boundaries where most callers borrow and few need ownership. Do not spread it through APIs without evidence; it adds branching and type complexity.
  • SmallVec and other inline collections help only when the observed length distribution usually fits inline. Their inline storage increases value and stack size.
  • Inspect hot type sizes with size_of, -Zprint-type-sizes, or a type-size tool. Box a rare large enum variant only when shrinking the common value improves the workload.
  • Reuse buffers across iterations when ownership is clear. Do not pool small values whose allocation is not visible in a profile.

For maps, use the entry API to avoid duplicate lookups and reserve capacity when the size is known. Alternative hashers trade collision resistance, dependencies, and portability for throughput; use them only for trusted keys after representative key-distribution benchmarks.

Iterators, strings, and bytes

  • Implement an accurate size_hint for custom iterators consumed by collect or extend.
  • Avoid collect when the result is immediately iterated once; keep the chain lazy. Collect when ownership, sorting, indexing, or reuse requires it.
  • Prefer iterating slices to repeated indexing in hot loops; this often makes bounds-check elimination easier.
  • Reuse String, Vec<u8>, or bytes::BytesMut buffers instead of formatting or allocating per item. Work with &[u8] for genuinely byte-oriented data; do not discard Unicode or path semantics for speed.
  • Keep Path/OsStr values native. Avoid string conversion solely for comparison or joining.

I/O

Use BufReader/BufWriter for repeated small operations and choose capacity from observed request sizes. Batch writes and reuse read buffers. Preserve partial-write handling, flush errors, EOF behavior, and output ordering; a faster path that changes them is incorrect.

Async and concurrency

  • Never perform long CPU work, blocking syscalls, or synchronous sleeps on an async executor thread. Use spawn_blocking for bounded blocking work and Rayon or a dedicated pool for sustained CPU parallelism.
  • Prefer std::sync::Mutex when the guard never crosses .await; use an async mutex only when it must. Profile contention before replacing either.
  • Use bounded channels when producers can outrun consumers. Select mpsc, oneshot, watch, or broadcast from message semantics, not benchmark folklore.
  • Audit cancellation and losing select! branches before batching or buffering. Work discarded on cancellation is still cost.
  • Account for oversubscription when Tokio blocking threads, Rayon, native libraries, and application threads share the same CPUs.
  • Check task ownership, queue depth, and shutdown. Leaked tasks and unbounded queues commonly look like memory or latency regressions.

Invoke runtime-process-debugging for process, pipe, EOF, child-lifecycle, or shutdown stalls.

Build and low-level tuning

Benchmark lto, codegen-units, opt-level, panic strategy, allocator, PGO, and linker changes separately. target-cpu=native is valid only for fixed deployment hardware; it makes distributed artifacts non-portable.

Before adding SIMD or unsafe, verify the hot instruction sequence and whether the compiler already removed bounds checks or vectorized it. Prefer safe loop shapes first. Every unsafe optimization needs a precise invariant, tests that exercise it, and a measured win over the safe version.

Regression guards

Use a focused Criterion benchmark, allocation guard, or compile-time size assertion only for the invariant that matters. Keep wall-time on shared CI advisory unless hardware and variance are controlled. Pin the Rust toolchain when compiler code generation affects the comparison.

Related skills

  • rust-specialist takes precedence for correctness, safety, and API design.
  • code-review checks a completed diff. When invoked from code-review, do not invoke it again.
  • code-simplifier runs after the gain is proven and preserves measured behavior.
  • runtime-process-debugging owns process and lifecycle stalls.

Correctness and safety override performance.

Signals

GitHub stars
220
Forks
11
Last commit
Sep 2026
Advanced
Item type
skill
Key
rust-performance
Source
github.com/caarlos0/dotfiles