如何使用SSE2添加数组中的所有元素?

时间:2012-10-01 20:35:11

标签: c x86 sse simd sse2

假设我有一个非常简单的代码,如:

double array[SIZE_OF_ARRAY];
double sum = 0.0;

for (int i = 0; i < SIZE_OF_ARRAY; ++i)
{
    sum += array[i];
}

我基本上想要使用SSE2进行相同的操作。我怎么能这样做?

1 个答案:

答案 0 :(得分:6)

这是一个非常简单的SSE3实现:

#include <emmintrin.h>

__m128d vsum = _mm_set1_pd(0.0);
for (int i = 0; i < n; i += 2)
{
    __m128d v = _mm_load_pd(&a[i]);
    vsum = _mm_add_pd(vsum, v);
}
vsum = _mm_hadd_pd(vsum, vsum);
double sum = _mm_cvtsd_f64(vsum0);

您可以通过使用多个累加器隐藏FP添加的延迟来展开循环以获得更好的性能(如@Mysticial所示)。使用多个“sum”向量展开3或4次以加载瓶颈和FP-add吞吐量(每个时钟周期一个或两个)而不是FP-add延迟(每3或4个循环一个):

__m128d vsum0 = _mm_setzero_pd();
__m128d vsum1 = _mm_setzero_pd();
for (int i = 0; i < n; i += 4)
{
    __m128d v0 = _mm_load_pd(&a[i]);
    __m128d v1 = _mm_load_pd(&a[i + 2]);
    vsum0 = _mm_add_pd(vsum0, v0);
    vsum1 = _mm_add_pd(vsum1, v1);
}
vsum0 = _mm_add_pd(vsum0, vsum1);    // vertical ops down to one accumulator
vsum0 = _mm_hadd_pd(vsum0, vsum0);   // horizontal add of the single register
double sum = _mm_cvtsd_f64(vsum0);

请注意,假设数组a是16字节对齐的,并且假设元素n的数量是2的倍数(或者在展开的循环的情况下为4)。

另请参阅Fastest way to do horizontal float vector sum on x86了解在循环外执行水平和的其他方法。 SSE3的支持并不是完全通用的(特别是AMD的CPU后来支持它比英特尔)。

此外,即使在支持它的CPU上,_mm_hadd_pd通常也不是最快的方式,因此在现代CPU上,仅SSE2版本不会更糟。不过,它在循环之外并没有太大的区别。