简介
SIMD(Single Instruction Multiple Data)是指一条指令能够操作多个数据,是对CPU指令的扩展,主要用来进行小数据的并行操作。
Intel最初支持SIMD的指令集是1996年集成在Pentium里的MMX(Multi-Media Extension,多媒体扩展),它的主要目标是为了支持MPEG视频解码。Intel在1999年Pentium3中推出了SSE(Streaming SIMD Extensions,流式SIMD扩展),是继MMX的扩展指令集,主要用于3D图形计算。Intel在2008年3月份提出了AVX指令集(Advanced Vector Extension,高级向量扩展),它是SSE延伸架构,将SSE中的16个128位XMM寄存器扩展位16个256位YMM寄存器,增加了一倍的运算效率。 (CSAPP)
实现
使用avx指令加速代码主要有两种方式:
自动向量化:intel C++ 编译器进行自动向量化,需要使用 -xhost 编译选项。在 gcc 编译器中的对应选项为 -march=native。开启该选项后,编译器会自动根据 CPU 支持的指令集进行向量化。
手动向量化:主要使用intel intrinsics中的指令进行代码程序的编写
自动向量化
对于代码的指定部分进行向量化可以加上编译宏。
1
2
3
4
5
6
7
void auto(float* a, float* b, int* idx) {
#pragma ivdep
#pragma vector always
for (int j = 0; j < 1024; ++ j) {
a[idx[j]] += b[j];
}
}
速度比较。当使用icc -O3 -xhost
的编译指令时,运行100m次,相较于g++ -O3
,向量化的编译方式能够获得2倍左右的速度提升。
观察输出文件的汇编代码,可以看到向量化指令的使用:
402ee0: c4 81 7c 10 14 88 vmovups (%r8,%r9,4),%ymm2
402ee6: c4 81 7c 10 64 88 20 vmovups 0x20(%r8,%r9,4),%ymm4
402eed: c5 fc 57 c0 vxorps %ymm0,%ymm0,%ymm0
402ef1: 62 f1 7d 08 74 c8 vpcmpeqb %xmm0,%xmm0,%k1
402ef7: c5 f4 57 c9 vxorps %ymm1,%ymm1,%ymm1
402efb: 62 f2 7d 29 92 04 97 vgatherdps (%rdi,%ymm2,4),%ymm0
402f02: 62 f1 7d 08 74 d0 vpcmpeqb %xmm0,%xmm0,%k2
402f08: 62 f1 7d 08 74 d8 vpcmpeqb %xmm0,%xmm0,%k3
402f0e: 62 f1 7d 08 74 e0 vpcmpeqb %xmm0,%xmm0,%k4
402f14: c4 a1 7c 58 1c 8e vaddps (%rsi,%r9,4),%ymm0,%ymm3
402f1a: 62 f2 7d 2a 92 0c a7 vgatherdps (%rdi,%ymm4,4),%ymm1
402f21: c4 a1 74 58 6c 8e 20 vaddps 0x20(%rsi,%r9,4),%ymm1,%ymm5
402f28: 49 83 c1 10 add $0x10,%r9
402f2c: 62 f2 7d 2b a2 1c 97 vscatterdps %ymm3,(%rdi,%ymm2,4)
402f33: 62 f2 7d 2c a2 2c a7 vscatterdps %ymm5,(%rdi,%ymm4,4)
手动向量化
下面是相同代码的手动向量化的实现:
1
2
3
4
5
6
7
8
9
10
11
12
void core_intrinsics(std::vector<float>& a, std::vector<float>& b, int* idx) {
for (int j = 0; j < 1024; j += 16) {
__m512i _idx = _mm512_load_epi32(idx + j);
__m512 _b512 = _mm512_load_ps(b.data() + j);
__m512 _a512 = _mm512_i32gather_ps(_idx, a.data(), 4);
__m512 _res = _mm512_add_ps(_a512, _b512);
_mm512_i32scatter_ps(a.data(), _idx, _res, 4);
}
}
有兴趣的可以自己尝试下,比较两者方法的速度以及背后的原因。
结论
通过上面的简单实验,我们初步认知了向量化的两种实现方式,但是如何能更好的利用avx指令的优势,需要考虑具体的代码。影响性能的核心因素是memory bound还是compute bound,代码程序是否能实现指令上的并行,代码的关键路径是什么等等都是我们需要考虑的。