浮点向量的 SSE 缩减

Posted

技术标签:

【中文标题】浮点向量的 SSE 缩减【英文标题】:SSE reduction of float vector 【发布时间】:2013-07-19 15:04:53 【问题描述】:

如何使用 sse 内在函数获取浮点向量的总和元素(减少)?

简单的序列号:

void(float *input, float &result, unsigned int NumElems)

     result = 0;
     for(auto i=0; i<NumElems; ++i)
         result += input[i];

【问题讨论】:

你有什么尝试吗? 你真的看过生成的代码吗?至少我对 gcc 的经验是,它在可能的情况下在执行 SSE 指令方面做得很好——但它可能需要 -O3。 【参考方案1】:

通常,您会在循环中生成 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;

注意:对于上面的例子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)。 @PeterCordes 你能用内存操作数解释一下_mm_add_ps吗?你的意思是指针参数而不是__m128 @user2023370: addps asm 指令,而不是 _mm_add_ps 内在函数。我说的是编译器的代码生成选项。例如addps xmm0, [rdi](将_mm_load_ps 折叠成可以在解码器中进行微融合的内存操作数)而不是movups xmm1, [rdi] / addps xmm0, xmm1。 (_mm_loadu_ps 不能折叠,除非您有 AVX 以允许未对齐的内存操作数。)当然,您仍然肯定需要多个累加器来隐藏 FP 添加延迟:请参阅Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? 了解更多信息。 谢谢。这很有帮助。我也读了你对另一个问题的回答。我可以检查一下我的理解吗:如果我在上面的答案中添加一个累加器,我们可能会有一个 v1v2,以及一个 vsum1vsum2,并且循环增量为 8 而不是 4?

以上是关于浮点向量的 SSE 缩减的主要内容,如果未能解决你的问题,请参考以下文章

SSE 的整数/浮点值

SSE/AVX 向量类型的差异

在汇编中将无符号字符转换为浮点数(为浮点向量计算做准备)

可以使用 movss 指令替换整数数据吗?

SSE2 向量移位

使用 SSE 缩放复杂向量