使用vaddss而不是addss在标量矩阵加法中有什么好处?

5

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

#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矩阵的最佳时间为0.000024秒,在406490次重复中。
movss   xmm1, DWORD PTR A[rcx+rax]
addss   xmm1, DWORD PTR B[rcx+rax]
movss   DWORD PTR C[rcx+rax], xmm1

gcc -O2 -mavx: 在重复执行1000001次128X128矩阵的情况下,最佳时间为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);
            }
        }

在标量程序中,使用 -mavx 比使用 msse4.2 的加速比为 2.7 倍。我知道 avx 提高了 ISA 的效率,可能是因为这些改进。但当我分别使用 AVXSSE 的内部函数实现该程序时,加速比为 3 倍。问题是:在标量模式下,使用 AVX 比 SSE 快 2.7 倍,但矢量化后加速比为 3 倍(此问题的矩阵大小为 128x128)。这有意义吗?尽管在标量模式下使用 AVX 和 SSE 会产生 2.7 倍的加速,但矢量化方法必须更好,因为我在 AVX 中处理八个元素,而在 SSE 中只处理四个元素。所有程序都缺少小于 4.5% 的缓存未命中,如 perf stat 报告的那样。

使用 gcc -O2linux mintskylake

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


回答标题问题(我无法完全解析正文的最后一部分):GCC仅在以-O1编译时执行您所说的操作。当针对具有AVX系统的系统时,始终使用传统SSE指令的VEX版本是一个好主意 - Margaret Bloom
@MargaretBloom,向量化是通过-ftree-loop-vectorize启用的,该选项在启用-O3但未启用-O2时启用。即使使用-O1 -ftree-loop-vectorize也会进行向量化。 - Z boson
@Zboson 说实话,我不理解重点在哪里。我本来期望 OP 在进行性能分析时使用 -O3(我认为没有必要挑剔我的观察中错误的“only”部分)。对于 OP:我也不理解你的意思:“使用 -mavx 比 msse4.2 快 2.7 倍”似乎与“标量-SSE 比标量-AVX 快 2.7 倍”相矛盾,而且你还明确地要求 GCC 生成 VEX 版本(GCC 在使用 -mavx 时抑制 SSEx 指令。当需要时,它会生成新的 AVX 指令或 AVX 等效指令。),我给了你一个解释这个决策的链接。 - Margaret Bloom
1
@MargaretBloom,我同意我不明白重点。OP的声明令人困惑,更新似乎矛盾。在这种情况下,我没有看到任何好的理由,标量SSE或AVX代码会有显着的差异。到目前为止,我无法使用GCC 6.2,Ubuntu 16.10,Skylake复制OP的结果。我想也许OP正在看到这个。 - Z boson
2
很抱歉再次强调,但我刚意识到一个解决方案是只使用AVX进行编译,不必担心非VEX编码。您无法在系统上测试仅使用SSE的代码,因为您没有仅支持SSE的系统。如果要比较128位和256位操作,可以尝试使用-mprefer-avx128。使用__asm__ __volatile__(“vzeroupper”:::);的问题在于它会在没有AVX的系统上崩溃。这就是为什么GCC除了asm之外不允许您这样做的原因。如果使用该指令,最好使用-mavx进行编译。 - Z boson
显示剩余37条评论
1个回答

4
您所遇到的问题在这里有解释(链接)。在Skylake系统上,如果AVX寄存器的上半部分是脏的,则非vex编码的SSE操作在AVX寄存器的上半部分存在虚假依赖关系。在您的情况下,您的glibc 2.23版本似乎存在一个错误。在我的Skylake系统上,使用Ubuntu 16.10和glibc 2.24,我没有这个问题。您可以使用。
__asm__ __volatile__ ( "vzeroupper" : : : ); 

清空AVX寄存器的上半部分。我认为您不能使用内置函数如_mm256_zeroupper来解决这个问题,因为GCC会说它是SSE代码而不识别内置函数。选项-mvzeroupper也行不通,因为GCC再次认为它是SSE代码,并且不会发出vzeroupper指令。

顺便说一句,这是微软导致硬件存在这个问题的错


更新:

其他人显然在Skylake上遇到了这个问题。它已被观察到在printfmemsetclock_gettime之后。

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


根据ABI,每个使用AVX的函数在执行完毕后都应该执行vzeroupper。看起来这个bug出现在其他地方。 - fuz
@fuz,你看了我指向的第一个链接吗?当清除AVX寄存器的上半部分时,问题就会消失。我无法在我的系统上重现这个问题,所以我无法测试它。OP说,在main之后使用__asm__ __volatile__("vzeroupper":::);不能解决问题,这是我预期的,但在clock_gettime之后使用它可以解决问题。在我的答案中,我没有提到这一点,因为我唯一相当确定的事情是问题出在上半部分被污染了。我们能同意这一点吗? - Z boson
阅读您链接的帖子的最后几行,它基本上说了我所说的相同的事情:有人必须在执行AVX指令后没有执行vzeroupper - fuz
@fuz,在那个链接中,bug出现在_dl_runtime_resolve_avx(), /lib64/ld-linux-x86-64.so.2中。 - Z boson

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