Table of Contents
GCC Vectorization
Vectorization enables GCC to use SIMD (Single Instruction Multiple Data) instructions—AVX2, AVX-512, NEON, etc.—to process multiple data elements in parallel. A loop that processes one array element per iteration can be transformed to process 4-8 elements simultaneously, massively increasing throughput.
gcc -O3 -march=native -o app app.c # use native CPU's SIMD instructions
By default, GCC compiles for a conservative baseline (x86-64) that runs on any machine but doesn't use modern SIMD. -march=native tells GCC to use whatever SIMD instructions the build machine has: AVX2 on modern x86, NEON on ARM, etc. Unsafe to redistribute (the binary may crash on older CPUs) but essential for benchmarking and HPC.
Specific target architectures:
gcc -O3 -march=znver2 -o app app.c # AMD Ryzen (Zen 2) gcc -O3 -march=skylake -o app app.c # Intel Skylake gcc -O3 -march=armv8-a+simd -o app app.c # ARM with NEON
Use the exact CPU model if you know it; -march=native auto-detects.
Vectorization reports: Use -fopt-info-vec-missed to see which loops GCC successfully vectorized and which it couldn't (and why):
gcc -O3 -march=native -fopt-info-vec-missed hot_loop.c
Output shows: “loop vectorized” or “loop not vectorized: reason X”. Common blockers: data dependencies, irregular memory access patterns, function calls in the loop.
Data layout matters: Vectorization is most efficient with simple, regular access patterns:
// Vectorizable: unit stride (sequential access) for (int i = 0; i < n; i++) result[i] = a[i] + b[i]; // Not vectorizable: irregular stride for (int i = 0; i < n; i++) result[i] = data[indices[i]]; // indirect access
Sequential access (unit stride) is easy to vectorize. Indirect or strided access defeats vectorization. Structure-of-arrays is often more vectorizable than array-of-structures.
Explicit SIMD intrinsics: If GCC can't auto-vectorize, write SIMD code manually using intrinsics:
#include <immintrin.h> __m256 a = _mm256_loadu_ps(data); // load 8 floats __m256 b = _mm256_loadu_ps(data+8); __m256 c = _mm256_add_ps(a, b); // add all 8 pairs _mm256_storeu_ps(result, c);
This gives fine-grained control but is verbose. Use only when auto-vectorization fails and you've measured that it matters.
Compiler pragmas: Hint to the compiler that a loop is vectorizable:
#pragma omp simd for (int i = 0; i < n; i++) result[i] = a[i] * b[i];
#pragma omp simd tells GCC “this loop has no dependencies, please vectorize it aggressively.” Use when you know the loop is safe but the compiler's analysis is conservative.
