In this lesson, you will learn how to inspect the machine code output by the Rust compiler to verify that your performance-critical abstractions translate into the efficient instruction sequences you expect.
When writing high-performance unsafe code or low-level primitives, the compiler’s optimizer is your best ally, but it can occasionally introduce unexpected overhead or fail to vectorize a loop as intended. Analyzing generated assembly allows you to confirm that zero-cost abstractions remain truly zero-cost.
Using cargo-show-asm
The most effective way to analyze your code is via the cargo-show-asm tool. By running cargo asm <path-to-function>, you can view the exact assembly instructions generated for your target architecture.
Consider a simple function performing a pointer-based summation:
pub unsafe fn sum_unchecked(data: *const i32, len: usize) -> i32 {
let mut sum = 0;
for i in 0..len {
sum += *data.add(i);
}
sum
}
What to Look For
When you inspect the generated output, you aren't just looking for "correct" logic; you are looking for optimal patterns:
- Loop Unrolling: Check if the compiler has unrolled your loop to reduce branch overhead. You should see multiple additions per iteration if the compiler successfully vectorized the code.
- Instruction Selection: Verify if the compiler is using SIMD instructions (like
vpxororvpadddon x86_64) instead of scalar arithmetic. - Redundant Memory Access: Look for excessive
movinstructions. If you see unnecessary movement between registers and the stack, it often indicates the compiler failed to keep a variable in a register, potentially suggesting you should use a more "optimization-friendly" data layout. - Inlining: Ensure that small helper functions were indeed inlined; if you see a
callinstruction to a trivial function, the optimizer may have missed an inlining opportunity, which you could address with#[inline(always)].
By comparing your high-level Rust logic to the resulting assembly, you gain the ability to spot where abstractions "leak" performance, allowing you to refactor your unsafe code to better match the underlying CPU architecture's pipeline.
Key Takeaway: Analyzing generated assembly is the final verification step to ensure your Rust code compiles to the most efficient machine instructions possible, bridging the gap between high-level safety and low-level performance.