在标量矩阵加法中使用 vaddss 而不是 adds 有啥好处?

Posted

技术标签:

【中文标题】在标量矩阵加法中使用 vaddss 而不是 adds 有啥好处?【英文标题】:What is the benefits of using vaddss instead of addss in scalar matrix addition?在标量矩阵加法中使用 vaddss 而不是 adds 有什么好处? 【发布时间】:2017-02-19 08:06:39 【问题描述】:

我已经实现了标量矩阵加法内核。

#include <stdio.h>
#include <time.h>
//#include <x86intrin.h>

//loops and iterations:
#define N 128
#define M N
#define NUM_LOOP 1000000


float   __attribute__(( aligned(32))) A[N][M],
        __attribute__(( aligned(32))) B[N][M],
        __attribute__(( aligned(32))) C[N][M];

int main()

int w=0, i, j;
struct timespec tStart, tEnd;//used to record the processiing time
double tTotal , tBest=10000;//minimum of toltal time will asign to the best time
do
    clock_gettime(CLOCK_MONOTONIC,&tStart);

    for( i=0;i<N;i++)
        for(j=0;j<M;j++)
            C[i][j]= A[i][j] + B[i][j];
        
    

    clock_gettime(CLOCK_MONOTONIC,&tEnd);
    tTotal = (tEnd.tv_sec - tStart.tv_sec);
    tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;
    if(tTotal<tBest)
        tBest=tTotal;
     while(w++ < NUM_LOOP);

printf(" The best time: %lf sec in %d repetition for %dX%d matrix\n",tBest,w, N, M);
return 0;

在这种情况下,我用不同的编译器标志编译了程序,内循环的汇编输出如下:

gcc -O2 msse4.2: 最佳时间:128X128 矩阵 406490 次重复 0.000024 秒

movss   xmm1, DWORD PTR A[rcx+rax]
addss   xmm1, DWORD PTR B[rcx+rax]
movss   DWORD PTR C[rcx+rax], xmm1

gcc -O2 -mavx: 最佳时间:128X128 矩阵 1000001 次重复 0.000009 秒

vmovss  xmm1, DWORD PTR A[rcx+rax]
vaddss  xmm1, xmm1, DWORD PTR B[rcx+rax]
vmovss  DWORD PTR C[rcx+rax], xmm1

AVX 版本gcc -O2 -mavx:

__m256 vec256;
for(i=0;i<N;i++)   
    for(j=0;j<M;j+=8)
        vec256 = _mm256_add_ps( _mm256_load_ps(&A[i+1][j]) ,  _mm256_load_ps(&B[i+1][j]));
        _mm256_store_ps(&C[i+1][j], vec256);
            
        

SSE 版本gcc -O2 -sse4.2::

__m128 vec128;
for(i=0;i<N;i++)   
    for(j=0;j<M;j+=4)
    vec128= _mm_add_ps( _mm_load_ps(&A[i][j]) ,  _mm_load_ps(&B[i][j]));
    _mm_store_ps(&C[i][j], vec128);
            
        

在标量程序中,-mavxmsse4.2 的加速比是 2.7 倍。我知道avx 有效地改进了 ISA,这可能是因为这些改进。但是当我在 AVXSSE 的内在函数中实现程序时,加速是 3 倍。问题是:当我对它进行矢量化时,AVX 标量比 SSE 快 2.7 倍,速度提高了 3 倍(这个问题的矩阵大小为 128x128)。是否有意义在标量模式下使用 AVX 和 SSE 时,可产生 2.7 倍的加速。但是矢量化方法必须更好,因为与 SSE 中的四个元素相比,我在 AVX 中处理了八个元素。正如perf stat 报告的那样,所有程序的缓存未命中率都低于 4.5%。

使用gcc -O2linux mintskylake

更新:简而言之,Scalar-AVX 比 Scalar-SSE 快 2.7 倍,但 AVX-256 在矢量化时仅比 SSE-128 快 3 倍。我认为这可能是因为流水线。在标量中,我有 3 个 vec-ALU 在矢量化模式下可能无法使用。我可能会比较苹果和橙子而不是苹果和苹果,这可能是我无法理解原因的一点。

【问题讨论】:

回答标题问题(我无法完全解析正文的最后一部分):GCC does what you said only when compiling at -O1。当使用 AVX 的目标系统为 always a good idea to use the VEX versions of the legacy SSE instructions。 @MargaretBloom,没有gcc -O2我添加到问题中。定位还可以,但我将纯 AVXSSE 而非 AVX-256 与 AVX-128 进行比较。 @MargaretBloom,矢量化由-ftree-loop-vectorize 启用,-O3 启用,但-O2 未启用。这甚至可以使用-O1 -ftree-loop-vectorize 进行矢量化 @MargaretBloom,我同意我不明白这一点。 OP 的说法令人困惑,更新似乎是矛盾的。在这种情况下,我看不出任何好的理由,标量 SSE 或 AVX 代码会产生重​​大影响。到目前为止,我无法使用 GCC 6.2、Ubuntu 16.10、Skylake 重现 OPs 结果。我想也许 OP 是seeing this。 对不起,我只是意识到一个解决方案是只用 AVX 编译,而不用担心非 vex 编码。您无法在系统上真正测试仅 SSE 的代码,因为您没有仅具有 SSE 的系统。如果您想比较 128 位和 256 位操作,可以尝试 -mprefer-avx128。使用__asm__ __volatile__ ( "vzeroupper" : : : ); 的问题在于它会在没有 AVX 的系统上崩溃。这就是为什么 GCC 不会让你这样做,除非使用 asm。如果您使用该指令,您不妨使用-mavx 进行编译。 【参考方案1】:

您所观察到的问题已在here 中得到解释。在 Skylake 系统上,如果 AVX 寄存器的上半部分是脏的,那么非 vex 编码的 SSE 操作对 AVX 寄存器的上半部分存在错误的依赖性。在您的情况下,您的 glibc 2.23 版本似乎存在错误。在我的带有 Ubuntu 16.10 和 glibc 2.24 的 Skylake 系统上,我没有遇到问题。你可以使用

__asm__ __volatile__ ( "vzeroupper" : : : ); 

清除 AVX 寄存器的上半部分。我认为您不能使用诸如 _mm256_zeroupper 之类的内在函数来解决此问题,因为 GCC 会说它是 SSE 代码并且无法识别内在函数。 -mvzeroupper 选项也不起作用,因为 GCC 再次认为它是 SSE 代码并且不会发出 vzeroupper 指令。

顺便说一句,it's Microsoft's fault that the hardware has this problem.


更新:

Other people are apparently encountering this problem on Skylake。在printfmemsetclock_gettime之后观察到了。

如果您的目标是将 128 位操作与 256 位操作进行比较,可以考虑使用-mprefer-avx128 -mavx(这在 AMD 上特别有用)。但是,您将比较 AVX256 与 AVX128,而不是 AVX256 与 SSE。 AVX128 和 SSE 都使用 128 位操作,但它们的实现不同。如果您进行基准测试,您应该提及您使用的是哪一个。

【讨论】:

根据 ABI,每个使用 AVX 的函数都应该在完成后执行vzeroupper。似乎该错误在其他地方。 @fuz,你读过我指向的第一个链接吗?清除 AVX 寄存器的上半部分后问题就消失了。我无法在我的系统上重现该问题,因此无法对其进行测试。 OP 说问题并没有在main 之后立即解决__asm__ __volatile__ ( "vzeroupper" : : : );,这是我所期望的,但是在clock_gettime 之后使用它之后它就消失了。在我的回答中我没有提到这一点,因为我唯一可以肯定的是问题是上半部分脏了。我们可以同意吗? 阅读你链接的帖子的最后几行,它说的基本上和我说的一样:一定有人使用了 AVX 指令,之后没有执行vzeroupper @fuz,在那个链接中,错误在_dl_runtime_resolve_avx(), /lib64/ld-linux-x86-64.so.2

以上是关于在标量矩阵加法中使用 vaddss 而不是 adds 有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV 函数学习14-图像与标量相加(cv2.add)

吴恩达《机器学习》课程总结_线性代数回顾

矩阵加法python实现

OpenCV 函数学习13-图像的加法运算(cv2.add)

设计一个三维向量类,并实现向量的加法,减法以及向量与标量的乘法和除法运算

内存是矩阵加法(SIMD 指令)的瓶颈吗?