Why compilers fail to vectorize loops
Dependencies, aliasing, alignment, control flow and profitability. The reasons automatic vectorization breaks down, and what each one looks like in source code.
A compiler will only vectorize a loop it can prove is safe to vectorize. Most failures are the compiler declining to make an assumption, not the compiler missing an opportunity.
1. Loop-carried dependencies
If iteration i reads a value iteration i-1 wrote, the iterations cannot execute simultaneously.
for (int i = 1; i < n; i++)
a[i] = a[i - 1] + b[i]; // true dependency, distance 1
Some patterns look like dependencies but are recognised and handled: sum reductions, min and max, and dependencies whose distance exceeds the vector length. A genuine short-distance dependency requires algorithmic change, not a pragma.
2. Possible aliasing
Given two pointers, the compiler usually cannot prove they address disjoint memory, so it must assume a store through one may be read through the other.
void axpy(float *out, const float *x, const float *y, int n) {
for (int i = 0; i < n; i++)
out[i] = x[i] * 2.0f + y[i]; // may out alias x or y?
}
Declaring the parameters restrict supplies the missing guarantee. It is a promise you must actually keep: if the buffers do overlap, the behavior is undefined. Compilers can also emit a runtime overlap check and two loop versions, which costs code size and a branch.
3. Control flow in the loop body
A branch that selects between values can be flattened into a masked or blended vector operation. A branch that exits early, calls a function, or throws generally cannot. Function calls block vectorization unless they inline or a vector variant exists.
4. Non-contiguous memory access
Unit-stride access maps to a single vector load. Large strides waste most of each cache line, and indirect access requires gather instructions whose throughput is often well below that of a contiguous load. Vectorizing an array-of-structures traversal frequently loses to converting the data to a structure-of-arrays layout first.
5. Unknown trip count and alignment
A loop whose bound is not affine, or whose induction variable is modified inside the body, cannot be analysed. Alignment matters less than it once did, since unaligned vector loads are cheap on current hardware, but a peel loop is still generated when alignment is unknown.
6. Floating-point semantics
Reassociating a floating-point reduction changes the result. Under strict IEEE semantics the compiler will not do it, so a sum reduction stays scalar until you allow reassociation with -ffast-math, -fassociative-math or an OpenMP reduction clause.
7. It vectorized, and chose not to
Cost models reject transformations they predict will be slower: short trip counts, expensive gathers, heavy masking, high register pressure. The report says the loop was not vectorized because it was not profitable, which is a different statement from cannot.
Read the report
gcc -O3 -fopt-info-vec-missed kernel.c
clang -O3 -Rpass-missed=loop-vectorize kernel.c
Both compilers name the loop and the reason. That message is the fastest route to the actual blocker.
Related tool: QCEV Vector Inspector examines a loop for dependencies, memory access patterns and likely vector operations.
References
- GCC documentation, Developer Options:
-fopt-infoand vectorization diagnostics. - LLVM documentation, Auto-Vectorization in LLVM and
-Rpassremarks. - Intel 64 and IA-32 Architectures Optimization Reference Manual.