使用sse和avx内部函数将一组打包的单值添加到一个值中

Using sse and avx intrinsics to add a set of packed singles into one value

本文关键字:添加 单值 一个 一组 avx sse 内部函数 使用      更新时间:2023-10-16

我有代码,我正在努力加快。首先,我使用了SSE特性,并看到了显著的收益。我现在正试图看看我是否可以做类似的与AVX的内在。从本质上讲,该代码取两个数组,根据需要加减它们,将结果平方,然后将所有这些平方相加。

下面是使用sse内部函数的简化版本:

float chiList[4] __attribute__((aligned(16)));
float chi = 0.0;
__m128 res;
__m128 nres;
__m128 del;
__m128 chiInter2;
__m128 chiInter;
while(runNum<boundary)
{
    chiInter = _mm_setzero_ps();
    for(int i=0; i<maxPts; i+=4)
    {
        //load the first batch of residuals and deltas
        res = _mm_load_ps(resids+i);
        del = _mm_load_ps(residDeltas[param]+i);
        //subtract them
        nres = _mm_sub_ps(res,del);
        //load them back into memory
        _mm_store_ps(resids+i,nres);
        //square them and add them back to chi with the fused
        //multiply and add instructions
        chiInter = _mm_fmadd_ps(nres, nres, chiInter);
    }
    //add the 4 intermediate this way because testing 
    //shows it is faster than the commented out way below
    //so chiInter2 has chiInter reversed
    chiInter2 = _mm_shuffle_ps(chiInter,chiInter,_MM_SHUFFLE(0,1,2,3));
    //add the two
    _mm_store_ps(chiList,_mm_add_ps(chiInter,chiInter2));
    //add again
    chi=chiList[0]+chiList[1];
    //now do stuff with the chi^2
    //alternatively, the slow way
    //_mm_store_ps(chiList,chiInter);
    //chi=chiList[0]+chiList[1]+chiList[2]+chiList[3];
}

这让我想到了我的第一个问题:是否有任何方法可以更优雅地完成最后一位(我在chiInter中使用4个浮点数并将它们求和为一个浮点数)?

无论如何,我现在正试图使用avx内部函数来实现这一点,这个过程的大部分是相当简单的,不幸的是,我正在拖延试图做最后一点,试图将8个中间chi值压缩成一个值。

下面是avx内部函数的一段简化代码:
float chiList[8] __attribute__((aligned(32)));
__m256 res;
__m256 del;
__m256 nres;
__m256 chiInter;
while(runNum<boundary)
{
    chiInter = _mm256_setzero_ps();
    for(int i=0; i<maxPts; i+=8)
    {
        //load the first batch of residuals and deltas
        res = _mm256_load_ps(resids+i);
        del = _mm256_load_ps(residDeltas[param]+i);
        //subtract them
        nres = _mm256_sub_ps(res,del);
        //load them back into memory
        _mm256_store_ps(resids+i,nres);
        //square them and add them back to chi with the fused
        //multiply and add instructions
        chiInter = _mm256_fmadd_ps(nres, nres, chiInter);
    }
    _mm256_store_ps(chiList,chiInter);
    chi=chiList[0]+chiList[1]+chiList[2]+chiList[3]+
        chiList[4]+chiList[5]+chiList[6]+chiList[7];
}

我的第二个问题是:是否有一些方法像我上面拉的SSE那样,让我更快地完成这个最后的加法?或者,如果有更好的方法来做我在SSE intrinsic中所做的事情,它是否有与AVX intrinsic等效的方法?

这个操作叫做水平求和。假设你有一个向量v={x0,x1,x2,x3,x4,x5,x6,x7}。首先,提取高/低部分,这样你就有了w1={x0,x1,x2,x3}w2={x4,x5,x6,x7}。现在调用_mm_hadd_ps(w1, w2),得到:tmp1={x0+x1,x2+x3,x4+x5,x6+x7}。同样,_mm_hadd_ps(tmp1,tmp1)得到tmp2={x0+x1+x2+x3,x4+x5+x6+x7,...}。最后一次,_mm_hadd_ps(tmp2,tmp2)给出tmp3={x0+x1+x2+x3+x4+x5+x6+x7,...}。您也可以将第一个_mm_hadd_ps替换为一个简单的_mm_add_ps

这些都是未经测试的,并且是从文档中编写的。也不保证速度…

有人在英特尔论坛上展示了另一个变体(寻找HsumAvxFlt)。

我们还可以通过编译gcc test.c -Ofast -mavx2 -S

来查看gcc的建议。
float f(float*t){
  t=(float*)__builtin_assume_aligned(t,32);
  float r=0;
  for(int i=0;i<8;i++)
    r+=t[i];
  return r;
}

生成的test.s包含:

vhaddps %ymm0, %ymm0, %ymm0
vhaddps %ymm0, %ymm0, %ymm1
vperm2f128  $1, %ymm1, %ymm1, %ymm0
vaddps  %ymm1, %ymm0, %ymm0

我有点惊讶最后一个指令不是vaddss,但我想这并不重要。