核心思想
// 标量: 一次算 1 个
for (int i = 0; i < 1024; ++i)
c[i] = a[i] + b[i]; // 1024 次加法
// SIMD: 一次算 4 个 (SSE) 或 8 个 (AVX)
// [a0,a1,a2,a3] + [b0,b1,b2,b3] = [c0,c1,c2,c3] ← 一条指令
// → 4 倍吞吐
SSE Intrinsics
#include <xmmintrin.h> // SSE
#include <emmintrin.h> // SSE2
// 加载 4 个 float → 128 位寄存器
__m128 va = _mm_loadu_ps(&a[i]);
__m128 vb = _mm_loadu_ps(&b[i]);
// 并行加法
__m128 vc = _mm_add_ps(va, vb);
// 存回
_mm_storeu_ps(&c[i], vc);
AVX2 — 256 位
#include <immintrin.h>
// 一次处理 8 个 float
__m256 va = _mm256_loadu_ps(&a[i]);
__m256 vb = _mm256_loadu_ps(&b[i]);
__m256 vc = _mm256_add_ps(va, vb);
_mm256_storeu_ps(&c[i], vc);
常用 Intrinsics 速查
| 操作 | SSE (4×float) | AVX (8×float) |
|---|
| 加 | _mm_add_ps | _mm256_add_ps |
| 减 | _mm_sub_ps | _mm256_sub_ps |
| 乘 | _mm_mul_ps | _mm256_mul_ps |
| 除 | _mm_div_ps | _mm256_div_ps |
| 平方根 | _mm_sqrt_ps | _mm256_sqrt_ps |
| 最大 | _mm_max_ps | _mm256_max_ps |
| 比较 | _mm_cmplt_ps | _mm256_cmp_ps |
编译器自动向量化
// 编译器在 -O2/-O3 自动尝试向量化
// 以下写法更容易被编译器向量化:
// ✅ 简单的计数循环
for (int i = 0; i < N; ++i) // 清晰的迭代次数
c[i] = a[i] + b[i];
// ❌ 复杂的控制流 — 阻止向量化
for (int i = 0; i < N; ++i) {
if (a[i] > 0) c[i] = a[i] + b[i];
else c[i] = a[i] - b[i];
}
// 查看编译器是否向量化了
// gcc -fopt-info-vec-optimized
// clang -Rpass=vectorize
何时值得手动 SIMD
自动向量化 80% 场景够用。
手动 SIMD 值得的 3 个条件:
1. 热点循环(profiling 确认)
2. 数据布局对 SIMD 友好(连续、对齐)
3. 编译器没向量化(查看机器码确认)