Loops are fundamental to repeating operations. However, at the machine instruction level, loops represent constant overhead. Every loop iteration requires the CPU to increment an iterator, compare it against a boundary, and branch back to the start. In this lesson, we will explore loop structures and see how compilers optimize away loop overhead entirely.
Let's look at loop types and compilation optimizations.
Standard Loop Constructs in C++
Modern C++ supports three main loop structures: while loops, do while loops, and for loops (including range based for loops).
For systems code, prefer range based for loops when iterating over containers, as they prevent boundary errors and help the compiler optimize memory accesses:
Loop Unrolling: Carpet Stretching for CPUs
When you write a loop that runs a small, fixed number of times, the overhead of checking the index and branching back can consume more CPU cycles than the actual work in the loop body.
To optimize this, compilers perform loop unrolling. The compiler copies the loop body multiple times to reduce or completely eliminate the loop checks.
- The Carpet Metaphor: Think of loop unrolling like a compiler getting tired of rolling and unrolling a carpet to inspect it one inch at a time, deciding instead to stretch the entire carpet flat on the floor to look at it all at once. It uses more space (larger binary file size) but allows you to read it in one continuous scan. The compiler is basically trading your hard drive space for raw, unadulterated speed.
Let's look at what happens under the hood when a loop is unrolled:
Unrolling can reduce loop-control work, but it can also increase code size and instruction-cache pressure. A compiler may unroll at an optimizing level when it judges the trade-off worthwhile; neither unrolling nor a performance gain is guaranteed.
Loop Invariants and Code Motion
Another optimization compilers perform inside loops is code motion. If you compute a value inside a loop that does not depend on the loop iterator, the compiler will hoist it out of the loop so it only executes once:
To write optimal code, help the compiler by marking variables outside loops as const or constexpr, making it clear that they do not change during iteration. If you don't, the compiler might just assume the worst and recalculate it every time.