Middle-end

Loops & Memory Optimization

Where the time actually goes: hoisting, unrolling, interchange, vectorization — and the aliasing and escape questions that decide whether any of it is allowed.

Loop-Invariant Code Motion
▶ lab

Move a computation whose result never changes out of the loop — but only if it is invariant AND either cannot trap or is guaranteed to execute at least once. That second condition is the one that turns a hoist into a fault the original program never had.

Q · The expression inside my loop clearly does not change. Why did the compiler leave it there?
Loop Unrolling
▶ lab

Duplicate the body so one iteration of the new loop does the work of several. It removes branches and exposes instruction-level parallelism, and it pays for both in code size and instruction-cache pressure — a trade whose sign depends on the trip count and the machine.

Q · Does unrolling a loop still help on a processor that predicts branches almost perfectly?
Fusion, Fission, Interchange and Tiling
▶ lab

Four restructurings that leave the computation identical and change the order in which memory is touched. Each buys a specific thing — fewer traversals, better vectorizability, unit-stride access, a working set that fits in cache — and each is legal only when it preserves every dependence in the original.

Q · The loop does the same arithmetic either way. Why does the order of the loops change the running time by an order of magnitude?
Automatic Vectorization
▶ lab

Turn a loop over scalars into a loop over vectors, doing several elements per instruction. It is legal only when no dependence is violated by processing elements together, and profitable only when the memory access pattern suits it — and the list of things that make a vectorizer give up is longer than the list of things that make it succeed.

Q · Why did my loop not vectorize, when the one next to it did?
Alias Analysis
▶ lab

Can these two references point to the same memory? Almost every optimization over memory is gated on that question, the honest answer is usually "maybe", and "maybe" means no. Aliasing is the single biggest limiter on what a compiler is allowed to do.

Q · Why does the compiler keep reloading a value from memory when nothing visibly writes to it?
Escape Analysis
▶ lab

Does this object outlive the scope that created it? If the compiler can prove it does not, the object can live on the stack, or be broken into registers and not exist at all — and the aliasing questions about it disappear with it.

Q · I allocated an object inside a function and never returned it. Does it still cost a heap allocation?
Bounds Check Elimination
▶ lab

A memory-safe language checks every array index. Removing the checks it can prove redundant is what makes safe languages fast — and it is why a loop written over a whole array is faster than the same loop written with index arithmetic the compiler cannot follow.

Q · If every array access in Rust and Java is bounds-checked, why is the generated code not slower than C?