Table of Contents
GCC Assembly
Reading generated assembly is the ground truth when debugging performance—it shows exactly what the CPU will execute. Use -S to generate assembly, and -fverbose-asm to annotate it with source variable names.
gcc -O3 -march=native -S -fverbose-asm hot_loop.c -o hot_loop.s cat hot_loop.s
The .s file contains x86-64 assembly (or ARM, depending on your target) with source annotations. Each # line is a comment mapping the following assembly back to source.
Example output (simplified):
# hot_loop.c:5: for (int i = 0; i < n; i++) xor %eax, %eax .L2: cmp %edx, %eax jge .L4 # hot_loop.c:6: result[i] = a[i] + b[i]; movl (%rcx,%rax,4), %r8d add (%r8,%rax,4), %r8d movl %r8d, (%r9,%rax,4) inc %eax jmp .L2 .L4:
xor %eax, %eax clears the loop counter. cmp %edx, %eax; jge .L4 checks if i >= n and jumps to the end. movl, add, movl perform the computation. inc %eax increments the counter. This repeats until the condition exits.
Analyzing for performance:
- Count instructions in the hot loop—more instructions = slower per iteration
- Look for unexpected memory operations—loads and stores are expensive
- Check for branch instructions (
jmp,jne)—branches can cause pipeline stalls - Verify vectorization happened—look for
vmovdqa,vpadd, etc. (AVX instructions)
No vectorization? If you expected -O3 -march=native to use AVX but see only scalar instructions, something blocked vectorization. Use -fopt-info-vec-missed to find out why.
Compiler explorer: Use godbolt.org to paste C code and see generated assembly interactively. Tweak code, change optimization levels, and see the assembly change in real-time. Great for learning.
Cross-reference with perf: After identifying a hot instruction in assembly, use [[perf]] annotate to see the actual sample counts on that instruction:
perf record -g ./app perf annotate -s hot_function
This combines profiling data with source/assembly, revealing exactly which instruction is a bottleneck.
Disassembly of compiled binaries:
objdump -d ./app | grep -A 30 hot_function
This disassembles the compiled binary (no source annotations, but shows the actual machine code executed).
Optimization hints: Strange assembly patterns sometimes reveal missed optimizations. Examples:
- Multiple branches in a hot loop—use conditional moves (cmov) instead
- Redundant memory operations—the compiler didn't eliminate dead stores
- Excessive function calls—aggressive inlining would help
