Skip to content
rusthacks.dev
Performance

Rust Performance Basics Measure First Then Change One Thing

The handful of habits behind most Rust speed: build in release, avoid needless allocation, use iterators and slices, pick the right collection and measure before tuning.

2 min read
Summarize in AI
Flat illustration for the post “Rust Performance Basics Measure First Then Change One Thing”, composed around perf.

There are about six things that account for most of the difference between slow and fast Rust, and none of them involve assembly. They are habits you can apply while writing, then confirm with a profile. This post lists them with the reason and a code example for each. It is the first stop after the profiling guide.

Rust 1.90, edition 2024.

1. Build with --release

Shell
cargo build --release

Debug builds disable inlining, keep overflow checks and skip most optimizations. The gap depends on the code, but a loop-heavy program can be many times slower in debug. Details in release versus debug.

2. Borrow, do not copy

rust
fn count_words(text: &str) -> usize {
    text.split_whitespace().count()
}

Taking &str means callers pass a String, a literal or a slice of a file buffer without any copy. A signature with String parameters forces clones at call sites; see String versus str.

3. Avoid allocating in loops

rust
let mut line = String::new();
for _ in 0..1000 {
    line.clear();
    read_into(&mut line);
    process(&line);
}

clear() keeps the capacity, so the buffer is reused. Creating a new String per iteration allocates and frees a thousand times. See reusing buffers.

4. Pre-size collections

rust
let mut out = Vec::with_capacity(items.len());

Without it a Vec grows by doubling, which copies on every doubling. If you know the final size, say so.

5. Buffer I/O

rust
use std::io::{BufWriter, Write};

let mut out = BufWriter::new(std::io::stdout().lock());
for i in 0..100_000 {
    writeln!(out, "{i}").unwrap();
}

Unbuffered writes cost a system call each. File and socket reads work the same way.

6. Choose the right data structure

A Vec lookup with contains inside a loop is quadratic. A HashSet makes it linear. Sorted Vec plus binary search beats a BTreeMap for read-mostly data. These choices dwarf the other items when the data is large.

What this list does not do

It does not make the code fast in the places that matter; a profile does. Measure first, as described in profiling Rust programs, then apply the habit that matches the hot spot. Clever tricks such as unsafe indexing or manual SIMD belong at the end, after algorithmic and allocation work. The official guidance is in the Rust Performance Book, under general tips.

Also inRust
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.