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

Posted

技术标签:

【中文标题】使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中【英文标题】:Using sse and avx intrinsics to add a set of packed singles into one value 【发布时间】:2014-03-28 19:55:57 【问题描述】:

我有代码要加快速度。首先,我使用了 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 个浮点数相加并将它们加到一个浮点数中)? p>

无论如何,我现在正在尝试使用 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 内在函数中所做的事情,它是否具有 AVX 内在函数的等价物?

【问题讨论】:

不要太担心最终总和的效率 - 假设 maxPts 相当大,那么总时间将由 for 循环内发生的事情以及任何前导码/后置码控制代码将与性能无关。 @PaulR,不幸的是,maxPts 很小,通常不超过 32。是的,尽管尺寸很小,但我看到使用 sse 与 naive 循环的巨大收益,即 144ns / 迭代 --> 14ns / 迭代。 查看相关:***.com/q/9775538/1918193。我很惊讶你没有尝试使用 haddps。要搜索的关键字:水平加法/总和。 @MarcGlisse,我没有,因为当我通过它时我不知道我在看什么。非常感谢您提供的信息。如果你把它写成答案,我很乐意接受。 如果 maxPts 很大,您可能需要检查 chi 中最终累积误差平方的指数,浮点数(单精度浮点)只有 24 位有效位 en.wikipedia.org/wiki/Single-precision_floating-point_format 作为您的累积错误增加,您将失去精度,并且可能会达到停止进一步累积的程度(残差的中间平方增量可能比您运行累积的chi 小 2^24 倍,并且当 CPU 归一化以添加它们时归零。 【参考方案1】:

此操作称为水平求和。假设你有一个向量v=x0,x1,x2,x3,x4,x5,x6,x7。首先,提取高/低部分,得到w1=x0,x1,x2,x3w2=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

这都是未经测试的,并且是从文档中编写的。也没有承诺速度......

Intel forum 上的某人显示了另一个变体(查找 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,但我想这并不重要。

【讨论】:

哇,这很有帮助。非常感谢你。我一直在通过搜索和测试来写我的改进,偶尔也会遇到一些事情。内在函数救了我,因为我真的不想编写内联汇编,但直到一周前我才知道它们。

以上是关于使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中的主要内容,如果未能解决你的问题,请参考以下文章

用于灰度到 ARGB 转换的 C++ SSE2 或 AVX2 内在函数

强制 AVX 内部函数改为使用 SSE 指令

Lanczos SSE/AVX 实施

C++ 中 SSE/AVX 的 x86 CPU 调度

矢量化代码中随机减速的原因

avx512中比较内在指令的不同语义?