Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
235: Profiling Rust Code with perf and flamegraph
How do I actually get perf to show me Rust functions instead of just hex addresses?
If you've tried running perf record on a Rust binary and then ran perf report only to see a wall of 0x00007f... addresses, you're not alone. It's frustrating. The problem is that by default, cargo build --release strips out the debug symbols to keep the binary small. Perf knows where the CPU is spending time, but it has no map to tell it which function name belongs to that address.
To fix this, you need to tell Cargo to keep the symbol information even in release mode. Add this to your Cargo.toml:
[profile.release]
debug = true
I always do this when I'm in the profiling phase. Don't worry, it doesn't slow down the execution speed of your code—it just makes the binary larger by including a symbol table. Now, when you run perf record ./target/release/my_app and then perf report, you'll actually see names like my_app::calculate_primes instead of random memory locations.
Are flamegraphs actually better than just reading the perf report?
In my experience, yes—by a mile. A perf report is a list; it tells you which functions are expensive, but it doesn't clearly show the call path. A flamegraph turns that data into a visual mountain range. The x-axis isn't time; it's the population of samples. The wider the bar, the more time the CPU spent in that function (or its children).
Instead of wrestling with perf manually, I recommend using cargo-flamegraph. It's a wrapper that handles the recording and the conversion to SVG for you. Install it with cargo install cargo-flamegraph, and then just run:
cargo flamegraph
When you open the resulting flamegraph.svg, look for the "plateaus"—those wide tops of the stacks. If you see a massive block for a function you didn't expect, that's your bottleneck. I've spent hours optimizing a loop only to realize via a flamegraph that the real culprit was actually a hidden allocation inside a library function I was calling.
Does the optimization level change what I see in the profile?
Absolutely. This is a common trap. If you profile a debug build, you're essentially profiling the compiler's lack of optimization. You'll see a massive amount of time spent in stack frame setup and tiny, un-inlined helper functions. It's noise. You'll waste your afternoon "fixing" things that the compiler would have optimized away in a release build anyway.
Always profile your --release build. However, be aware that --release enables inlining. If the compiler decides to inline a small function into its caller, that function effectively disappears from the flamegraph because it no longer has its own stack frame. If you suspect a specific small function is the problem but you can't see it, you can temporarily tell Rust not to inline it for the sake of the profile:
#[inline(never)]
fn expensive_small_task() {
// ...
}
Just remember to remove that attribute before you push to production, or you'll be leaving performance on the table.
📋 Practical Task
Optimizing a Naive Prime Finder with Flamegraph Analysis
You are given a Rust program that calculates the sum of all prime numbers up to 1,000,000 using a very inefficient "trial division" method. Your goal is to identify the exact bottleneck and optimize it.
The Setup:
- Create a project with a function
is_prime(n: u64) -> boolthat checks for primality by iterating from2ton - 1. - Call this function in a loop from 2 to 1,000,000.
- Ensure your
Cargo.tomlhasdebug = trueset for the release profile.
The Challenge:
- Run
cargo flamegraphand locate theis_primeblock. Notice how wide it is compared to the rest of the program. - Optimize the
is_primefunction (e.g., by checking only up to the square root ofnand skipping even numbers). - Run
cargo flamegraphagain. - Compare the two SVG files. You should see the
is_primeblock shrink dramatically in width relative to the total execution time.
There are no comments for now.