2012-10-01 37 views
4

假设我有一个非常简单的代码,如:如何使用SSE2添加数组中的所有元素?

double array[SIZE_OF_ARRAY]; 
double sum = 0.0; 

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

我基本上要做到使用SSE2相同的操作。我怎样才能做到这一点?

+0

如果你真的需要使用双精度,那么它可能不值得打扰,因为现代大多数现代x86 CPU都有两个FPU。如果你可以下降到单精度(即浮点),那么它可能是值得的。你需要多少性能提升? –

+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的建议),以获得更好的性能。与多个展开3次或4次“总和”向量瓶颈负载和FP-添加可以通过(一个或两个每一个时钟周期),而不是FP-添加延迟(每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后来支持它比Intel)。

而且,_mm_hadd_pd通常不是最快的方式,即使在支持它的CPU上也是如此,所以现代CPU上的SSE2版本不会更糟。尽管如此,它在循环之外,并且两种方式都没有太大的区别。

+0

我认为这可以从展开至少3次迭代中受益。 (3个单独的'vsum'变量) – Mysticial

+0

是的,可能。您可以让编译器展开它,或者手动做更好的工作。尽管性能可能会受到内存带宽的限制,除非它是一个相对较小的数据集恰好在缓存中,所以微观优化可能不会产生太多好处。 –

+0

我不认为编译器被允许节点拆分,因为它打破了关联性。这就是说我没有看到它在轻松的浮点下会做什么。但是我从来没有见过编译器在优化SSE内在函数方面过于积极。 – Mysticial