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.
Let's look at what happens under the hood when a loop is unrolled:
By unrolling the loop, the CPU executes four instructions in a straight line with zero branch prediction checks. Modern compilers (like GCC or Clang) will automatically perform loop unrolling when optimization flags (such as -O2 or -O3) are enabled.
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.