我可以用SSE指令来获取浮点向量元素的和(降维)。以下是简单的串行代码:
void(float *input, float &result, unsigned int NumElems)
{
result = 0;
for(auto i=0; i<NumElems; ++i)
result += input[i];
}
通常在循环中会生成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的倍数,则在函数末尾添加一个标量循环以累加剩余元素。ADDPS
,这可能会微型熔合,从而降低开销。此外,您可以通过使用多个累加器来获得2或4个依赖链,因此您的循环可以每个周期维持1个向量FP加法,而不是每个(ADDPS
的延迟= 3)。 - Peter Cordes_mm_add_ps
和内存操作数的关系吗?你是指一个指针参数而不是 __m128
吗? - user2023370addps
,而不是内部函数_mm_add_ps
。我在谈论编译器的代码生成选项。例如,将_mm_load_ps
折叠成可以微融合到解码器中的内存操作数,如addps xmm0,[rdi]
,而不是movups xmm1,[rdi]
/addps xmm0,xmm1
。(除非您拥有AVX以允许不对齐的内存操作数,否则无法折叠_mm_loadu_ps
。)当然,您仍然绝对需要多个累加器来隐藏FP加法延迟:请参见为什么Haswell上的mulss只需要3个周期,与Agner的指令表不同?获取更多信息。 - Peter Cordesv1
和v2
,以及一个vsum1
和vsum2
,循环增量为8而不是4? - user2023370