2012-10-01 76 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