Why Rust is fast, but not always?
\nRust is a language that promises C++ performance with memory safety. But even in Rust, you can write slow code. A couple of wrong decisions — and your service starts to lag under load. The good news: Rust gives you all the tools to find and eliminate bottlenecks. In this article, we'll break down how to profile Rust code and which optimization techniques actually work.
\n\nWe won't speak abstractly. You'll see real code examples, learn how to use perf, flamegraph, valgrind, and built-in benchmarks. After reading, you'll be able to independently conduct a performance audit of your project.
Toolkit for Profiling Rust
\nBefore optimizing anything, you need to understand what exactly is slowing down. Here are three working tools that will help you look inside the program.
\n\n1. Benchmarks with cargo bench
\nThe standard way to measure performance is to write a benchmark. In Rust, this is done using the criterion crate or the built-in test framework. Example:
use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 0,\n 1 => 1,\n _ => fibonacci(n - 1) + fibonacci(n - 2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nRun: cargo bench. The output will give you execution time, deviation, and comparison with previous runs. This is the baseline to start any optimization.
2. Profiling with perf and flamegraph
\nWhen you need to find out which functions are consuming the CPU, use perf. Collect the data, then visualize it as a flamegraph:
# Collect data\nperf record -g ./target/release/my_app\n# Generate flamegraph\nperf script | stackcollapse-perf.pl | flamegraph.pl > profile.svg\n\n\nThe flamegraph will show you the "hot" code sections. The wider the block, the more time is spent on that function. This is the most visual way to find a bottleneck.
\n\n3. Finding leaks with Valgrind
\nRust protects against memory leaks at the compilation level, but not against all of them. For example, if you use Rc or Arc with cyclic references, memory may not be freed. Valgrind will help detect this:
valgrind --tool=massif ./target/debug/my_app\n\n\nMassif will show how much memory the program consumes at each moment in time. If the graph doesn't drop after completion — look for a leak.
\n\nPractical Code Optimization Techniques in Rust
\nNow that you know what to look for, let's break down how to fix it. Here are five proven methods that actually speed up code.
\n\n1. Zero-cost abstractions: use iterators correctly
\nIterators in Rust compile into code as efficient as manual loops. But only if you don't make unnecessary copies. Compare:
\n\n// Bad: create Vec, copy, filter\nlet result: Vec<u32> = data.iter().filter(|x| x % 2 == 0).collect();\n// Good: work with iterator without intermediate collections\nlet sum: u32 = data.iter().filter(|x| x % 2 == 0).sum();\n\n\nThe second option does not allocate memory for a temporary vector. If you only need one value — don't collect.
\n\n2. Caching results
\nIf a function is called repeatedly with the same arguments, cache the result. Use HashMap or the lru crate to limit cache size:
use std::collections::HashMap;\n\nfn expensive_calc(x: u64) -> u64 {\n // ... long computation\n x * x\n}\n\nfn c