浮点向量的 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? 了解更多信息。
谢谢。这很有帮助。我也读了你对另一个问题的回答。我可以检查一下我的理解吗:如果我在上面的答案中添加一个累加器,我们可能会有一个 v1
和 v2
,以及一个 vsum1
和 vsum2
,并且循环增量为 8 而不是 4?
以上是关于浮点向量的 SSE 缩减的主要内容,如果未能解决你的问题,请参考以下文章