向量化 - SSE、AVX 和 AVX2 可实现加速

5
我正在进行有关在MacOS上使用以下i7处理器的向量化基准测试:

$ sysctl -n machdep.cpu.brand_string

Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz

我的 MacBook Pro 是中 2014 年的。

我尝试使用不同的标志选项进行矢量化:我感兴趣的有三个,分别是 SSE、AVX 和 AVX2。

对于我的基准测试,我将两个数组的每个元素相加,并将总和存储在第三个数组中。

我必须提醒您,我正在使用 double 类型来处理这些数组。

以下是我基准代码中使用的函数:

1*) 首先是 SSE 矢量化:

#ifdef SSE
#include <x86intrin.h>
#define ALIGN 16
void addition_tab(int size, double *a, double *b, double *c)
{

 int i;
 // Main loop
 for (i=size-1; i>=0; i-=2)
 {
  // Intrinsic SSE syntax
  const __m128d x = _mm_load_pd(a); // Load two x elements
  const __m128d y = _mm_load_pd(b); // Load two y elements
  const __m128d sum = _mm_add_pd(x, y); // Compute two sum elements
  _mm_store_pd(c, sum); // Store two sum elements

  // Increment pointers by 2 since SSE vectorizes on 128 bits = 16 bytes = 2*sizeof(double)
  a += 2;
  b += 2;
  c += 2;
 }

}
#endif

2*) 使用AVX256向量化的第二种方法:

#ifdef AVX256
#include <immintrin.h>
#define ALIGN 32
void addition_tab(int size, double *a, double *b, double *c)
{

 int i;
 // Main loop
 for (i=size-1; i>=0; i-=4)
 {
  // Intrinsic AVX syntax
  const __m256d x = _mm256_load_pd(a); // Load two x elements
  const __m256d y = _mm256_load_pd(b); // Load two y elements
  const __m256d sum = _mm256_add_pd(x, y); // Compute two sum elements
  _mm256_store_pd(c, sum); // Store two sum elements

  // Increment pointers by 4 since AVX256 vectorizes on 256 bits = 32 bytes = 4*sizeof(double)
  a += 4;
  b += 4;
  c += 4;
 }

}
#endif

对于SSE向量化,我预计速度提升大约为2倍,因为我将数据对齐在128位= 16字节= 2 * sizeof(double)上。

以下图表表示了我在SSE向量化方面得到的结果:

Results with SSE

所以,我认为这些结果是有效的,因为SpeedUp大约是2。

现在对于AVX256,我得到了以下图表:

Results with AVX256

对于AVX256向量化,我期望加速比约为4,因为我将数据对齐在256位= 32字节= 4 * sizeof(double)上。
但是正如您所看到的,我仍然获得了2倍而不是4倍的加速比。
我不明白为什么使用SSE和AVX向量化时我得到相同的加速比。
这是我为所有以上结果完成的编译命令行:
对于SSE:
gcc-mp-4.9 -DSSE -O3 -msse main_benchmark.c -o vectorizedExe

对于 AVX256:

gcc-mp-4.9 -DAVX256 -O3 -Wa,-q -mavx main_benchmark.c -o vectorizedExe

此外,使用我的处理器模型,我能使用AVX512向量化吗?(一旦解决了这个问题)。感谢您的帮助。
更新1
我尝试了@Mischa的不同选项,但仍然无法通过AVX标志和选项获得4倍的加速。您可以查看我的C源代码http://example.com/test_vectorization/main_benchmark.c.txt(带有.txt扩展名可直接在浏览器中查看),用于基准测试的shell脚本为http://example.com/test_vectorization/run_benchmark
如@Mischa所说,我尝试应用以下编译命令行: $GCC -O3 -Wa,-q -mavx -fprefetch-loop-arrays main_benchmark.c -o vectorizedExe 但生成的代码没有AVX指令。
如果您能够查看这些文件,那就太好了。谢谢。

https://dev59.com/Suk5XIcBkEYKwwoY_O6N#42972674 - Z boson
1
你的速度相对于什么而言?如果你使用 foo(int size, double *a, double *b, double *c) { for(int i=0; i<size; i++) c[i] = a[i] + b[i];}GCC 将使用 -O3 向量化 foo,所以我很惊讶你看到了任何加速。 - Z boson
不,我使用-O0标志和`#ifdef NOVEC void addition_tab(int size, double *a, double *b, double *c) { int i; // Classical sum for (i=0; i<size; i++) c[i] = a[i] + b[i];} #endif编译命令行为gcc-mp-4.9 -DNOVEC -O0 main_benchmark.c -o noVectorizedExe`。 - user1773603
你应该启用优化,至少传递 -O1gcc(最好使用 -O2-O3-march=native)。对未经优化的二进制文件进行基准测试是没有意义的。 - Basile Starynkevitch
2个回答

1
你的缓存到内存之间存在瓶颈。你的core7有一个64字节的缓存行。对于sse2,16字节的存储需要64字节的加载、更新和排队返回到内存。按升序进行的16字节加载可以从自动预取预测中获得一些加载优势。在下一个存储的256字节前添加目标内存的mm_prefetch。avx2的32字节存储也适用相同的方法。

@Mischa 谢谢,你能给我一个正确使用mm_prefetch的代码片段吗?我没有找到太多关于它的文档。 - user1773603

0

NP. 有以下几种选择:

(1) x86特定代码:#include <emmintrin.h> ... for (int i=size; ...) { _mm_prefetch(256+(char*)c, _MM_HINT_T0); ... _mm256_store_pd(c, sum);

(2) gcc特定代码: for (int i=size; ...) { __builtin_prefetch(c+32); ...

(3) gcc -fprefetch-array-loops --- 编译器最懂。

如果您的gcc版本支持,则(3)是最佳选择。 如果在同一硬件上编译和运行,则(2)是次佳选择。 (1)可移植到其他编译器。

不幸的是,“256”只是一个估计值,并且与硬件相关。128是最小值,512是最大值,具体取决于CPU:RAM速度。如果切换到_mm512*(),则将这些数字加倍。

如果你需要跨多种处理器工作,我建议以一种覆盖所有情况的方式编译,然后在运行时测试cpuid(ax = 0)>= 7,然后测试cpuid(ax = 7,cx = 0):bx & 0x04000010(0x10用于AVX2,0x04000000用于AVX512包括预取)。顺便说一下,如果你使用gcc并指定-mavx或-msse2,则编译器会为你定义内置宏__AVX__或__SSE2__;无需-DAVX256。为支持古老的32位处理器,-m32不幸地禁用了__SSE2__,因此有效地禁用了#include <emmintrin.h> :-P 希望对你有帮助。

我尝试了您提供的不同选项,但仍无法通过AVX标志和选项获得4倍的加速比。您可以查看我的C源代码,网址为http://beulu.com/test_vectorization/main_benchmark.c.txt(使用.txt扩展名可直接在浏览器中查看),以及用于基准测试的shell脚本,网址为http://beulu.com/test_vectorization/run_benchmark。如果您能够查看这些文件,那将是非常好的。谢谢。附注:我还在第一篇帖子下面的UPDATE 1中提供了这些文件。 - user1773603
1
需要几天时间才能确定一个iCore7。与此同时,我拿了你的代码,惊讶地发现SSE2双精度对操作仅比简单双精度快约12%。如果AVX双精度四元组操作接近SSE2,则手动预取不起作用;内部预取预测已经是最优的。嗯... - Mischa
@Mischa,你有没有其他方法可以在不使用向量化的情况下将顺序版本和双AVX版本的速度提高4倍?还是这可能是我的代码问题,无法获得这个因素?任何帮助都欢迎,谢谢。 - user1773603
抱歉我没有时间跟进。具体的硬件对此有很大影响 :-( 另一方面,如果您控制硬件,CUDA 能够胜过其他所有东西。 - Mischa
Haswell已经具有非常好的硬件预取功能,包括预取到下一页(IvyBridge中新增)和投机TLB加载。这是硬件预取的理想情况,软件预取不太可能起作用。如果我要尝试任何东西,我会尝试一些循环展开,以便更多的工作可以适应ROB中相同数量的uops,从而让乱序执行更好地隐藏延迟波动。 - Peter Cordes
Triad(c[i] = a[i]+b[i])主要是一个内存基准测试,并且使用double甚至标量也可以接近饱和内存带宽。(尽管gcc应该会在没有-ffast-math的情况下自动向量化它,所以也许这就是“标量”版本发生的事情,@youpilat13?) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接