# Rust Performance

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

- Skill: `caarlos0/rust-performance` (Agent Skill)
- Install (CLI): `npx skillmds@latest add caarlos0/rust-performance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/caarlos0/rust-performance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: caarlos0 (https://skillmd.com/u/caarlos0)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/caarlos0/rust-performance

---


# Rust Performance

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:

```toml
[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.

