浮动矢量的SSE减少

gor*_*ill 8 c++ sse sum simd reduction

如何使用sse intrinsics获得浮点向量的和元素(减少)?

简单的串口代码:

void(float *input, float &result, unsigned int NumElems)
{
     result = 0;
     for(auto i=0; i<NumElems; ++i)
         result += input[i];
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*l R 17

通常,您在循环中生成4个部分和,然后在循环之后在4个元素之间水平求和,例如

#include <cassert>
#include <cstdint>
#include <emmintrin.h>

float vsum(const float *a, int n)
{
    float sum;
    __m128 vsum = _mm_set1_ps(0.0f);
    assert((n & 3) == 0);
    assert(((uintptr_t)a & 15) == 0);
    for (int i = 0; i < n; i += 4)
    {
        __m128 v = _mm_load_ps(&a[i]);
        vsum = _mm_add_ps(vsum, v);
    }
    vsum = _mm_hadd_ps(vsum, vsum);
    vsum = _mm_hadd_ps(vsum, vsum);
    _mm_store_ss(&sum, vsum);
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

注意:对于上面的示例a必须是16字节对齐,并且n必须是4的倍数.如果a无法保证对齐,则使用_mm_loadu_ps而不是_mm_load_ps.如果n不保证是4的倍数,则在函数末尾添加标量循环以累积任何剩余元素.

  • 如果输入数组可能很大,那么在开始时也应该有一个标量循环,它运行0-3次,直到输入对齐SSE循环的16B边界为止。这样一来,您就不会有跨缓存/页面行的负载,从而降低循环速度。并且它可以将“ ADDPS”与一个内存操作数一起使用,这可能会产生微熔丝,从而减少开销。同样,通过使用多个累加器,您可以获得2或4个依赖链,因此您的循环可以在每个周期承受1个矢量FP加,而不是每个1个(“ ADDPS”的延迟= 3)。 (2认同)
  • @ user2023370:adds是asm指令,而不是内在函数_mm_add_ps。我说的是编译器的代码生成选项。例如`addps xmm0,rdi'(将'_mm_load_ps'折叠到可以在解码器中进行微熔丝的存储器操作数中),而不是`movups xmm1,rdi'/`addps xmm0,xmm1`。(除非具有AVX以允许未对齐的内存操作数,否则_mm_loadu_ps不会折叠。)当然,您仍然肯定希望多个累加器隐藏FP添加延迟:请参阅[为什么muls在Haswell上仅花费3个周期,与Agner的指令表不同?](// stackoverflow.com/q/45113527)了解更多。 (2认同)