Site Tools


wiki:gcc-vectorization

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.

wiki/gcc-vectorization.md · Last modified: by 127.0.0.1