Chapter 7 Summary and Quiz
Review the optimizer's core transformations and consolidate the reading skills from the whole course.
The optimizer recap
Congratulations, you have reached the end of the chapter and the end of the course. Chapter 7 put -O0 and -O2 listings side by side and studied the gap between them, because that gap is the optimizer's work. Let's review the key ideas from each lesson.
-O0 Versus -O2
-O0 means no optimization: the compiler translates each statement literally, giving every argument and local its own stack slot and constantly storing values then loading them straight back. -O2 keeps values in registers, drops the redundant store-then-reload traffic, and short functions often need no stack frame at all; sumRange fell from fourteen instructions to four. The optimizer never changes what a function computes, only how much overhead it pays. -O0 exists for easier debugging and faster builds; -O2 is the typical release setting.
Constant Folding and Propagation
Constant folding evaluates all-constant expressions at compile time, so 7 * 24 * 60 * 60 compiles to nothing but the answer:
mov eax, 604800
ret
Constant propagation tracks variables known to hold constants and substitutes their values wherever they are used, which often exposes further folding. Folded constants leave no trace of the original arithmetic or the named locals that held them, so readable intermediate variables cost nothing at -O2. The optimizer only folds what it can prove constant; values from std::cin or opaque arguments stay as real run-time instructions.
Inlining
Inlining replaces a call with a copy of the called function's body at the call site, so a small helper's work appears directly inside its caller with no jump, no return address, and no second prologue. The standalone copy of the helper may still be emitted for other callers. The biggest benefit is not avoiding call overhead but exposing the helper's code to the rest of the optimizer, letting folding and propagation cross the old function boundary. A missing call at -O2 usually means inlining; a call that survives means the optimizer chose not to inline.
Dead Code Elimination
Dead code elimination removes computations whose results are never used (unused results, sometimes called dead store elimination) and branches that can never execute (unreachable code, like an if (false) block). It is justified by the as-if rule: the compiler may do anything internally as long as observable behavior is as if your code ran exactly. Inlining and constant propagation constantly create dead code, and elimination is the cleanup pass that sweeps it away. Work with observable side effects, such as printing or modifying a global, is never removed just because its return value is ignored.
Loop Unrolling
Every loop iteration pays for its machinery: incrementing the counter, comparing against the limit, and jumping back. Loop unrolling does several iterations' worth of work per pass so that overhead is paid less often; in sumTo the counter advanced with add eax, 2 and one lea folded two values into the running total. Unrolling needs remainder handling (loop peeling) for counts that do not divide evenly, which shows up as extra setup code around a tidy main loop. It trades code size for speed and sets the stage for vectorisation.
A Glimpse of Vectorisation
Vectorisation rewrites a loop to use SIMD (Single Instruction Multiple Data) instructions that operate on several values packed into one wide register. The 128-bit xmm0..xmm15 registers hold four 32-bit ints, and packed instructions like paddd act on all four lanes at once; the eight-iteration scale loop became two groups of four with no loop left at all. Recognise vectorised code by xmm registers, XMMWORD PTR operands, and p-prefixed packed mnemonics. Auto-vectorisation requires provably independent iterations and regular memory access, so many loops stay scalar.
Capstone: Reading a Real Function
The capstone read a complete -O2 function with a guard, a loop with an early exit, a walking pointer, and an inlined helper fused into a single lea. The method matters more than the listing: start from the signature and pin the argument registers, map the labels and jumps to sketch the flow graph (a backward jump is a loop back-edge; forward jumps are guards or exits), find the loop's moving parts, decode the addressing modes, and follow the return register. Above all, read what is missing: no call means inlining, no stack frame means register-only, absent code means folding or dead code elimination, and no xmm loop means vectorisation was not possible or not worthwhile.
Key Terminology
- Optimization level: A compiler setting (
-O0,-O2) controlling how many optimizations are applied - -O0: No optimization; literal, step-by-step translation used for debug builds
- -O2: A broad set of optimizations; the typical release setting
- Stack slot: The memory home
-O0gives each argument and local variable - Constant folding: Evaluating an all-constant expression at compile time
- Constant propagation: Substituting a variable's known constant value wherever it is used
- Inlining: Copying a called function's body into the call site in place of a
call - Call site: The location in the caller where a function call appears
- Dead code elimination: Removing computations whose results are never used and branches that never execute
- As-if rule: The compiler may transform code freely as long as observable behavior is unchanged
- Unreachable code: Code the compiler can prove will never execute
- Loop unrolling: Performing several iterations' work per pass to pay the loop machinery less often
- Remainder handling (loop peeling): Extra code around an unrolled loop for counts that do not divide evenly
- Vectorisation (SIMD): Using wide registers to process several values with a single instruction
- xmm registers: The 128-bit registers
xmm0..xmm15, each holding four 32-bitints - Packed instruction: A SIMD instruction like
padddthat operates on all lanes of a wide register at once - Back-edge: The backward jump at the bottom of a loop, the signature of a loop in any listing
- Guard: An early forward jump that exits before the main work for a degenerate case
Where to Go from Here
You now have everything you need to keep reading on your own. The habit is the whole skill: paste code into Compiler Explorer, or read the assembly panel in this editor, and study what comes back. The x86-64 Quick Reference lesson in Chapter 2 remains a handy desk reference whenever a mnemonic or register slips your mind.
Some directions worth exploring next: compare compilers (GCC, Clang, MSVC) and flags (-O1, -O3, -Os) on the same function and study the differences; read the assembly of code you actually ship rather than toy examples, where inlining, folding, and vectorisation decisions have real consequences; and remember that reading assembly tells you what the compiler did, not where your program spends its time, so always profile before optimizing. The optimizer is very good at its job. Now you can watch it work.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Chapter 7 Summary and Quiz - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!