2011-04-01 14 views
12

下面是我嘗試使用SSE加速的示例C代碼,兩個數組長度爲3072個元素,雙精度型可能會降低到浮點數,如果我不需要雙精度。如何使用SSE指令集來實現絕對2倍或4次浮點數? (高達SSE4)

double sum = 0.0; 

for(k = 0; k < 3072; k++) { 
    sum += fabs(sima[k] - simb[k]); 
} 

double fp = (1.0 - (sum/(255.0 * 1024.0 * 3.0))); 

反正我現在的問題是如何做到在上交所晶圓廠一步註冊雙打或浮動,這樣我可以保持在上交所整個計算進行註冊,它仍然是快,我可以並行所有的通過部分展開此循環。

下面是我找到的一些資源fabs() asm或可能這flipping the sign - SO但是第二個的弱點需要一個條件檢查。

回答

2

也許最簡單的方法如下:

__m128d vsum = _mm_set1_pd(0.0);  // init partial sums 
for (k = 0; k < 3072; k += 2) 
{ 
    __m128d va = _mm_load_pd(&sima[k]); // load 2 doubles from sima, simb 
    __m128d vb = _mm_load_pd(&simb[k]); 
    __m128d vdiff = _mm_sub_pd(va, vb); // calc diff = sima - simb 
    __m128d vnegdiff = mm_sub_pd(_mm_set1_pd(0.0), vdiff); // calc neg diff = 0.0 - diff 
    __m128d vabsdiff = _mm_max_pd(vdiff, vnegdiff);  // calc abs diff = max(diff, - diff) 
    vsum = _mm_add_pd(vsum, vabsdiff); // accumulate two partial sums 
} 

注意,這可能不是任何速度比現代的x86 CPU,通常有兩個反正的FPU標碼。但是,如果您可以降低到單精度,那麼您的吞吐量可能會提高2倍。

還需要注意的是,在循環之後,您需要將vsum中的兩個部分和合併爲一個標量值,但這並不重要,並且不是性能關鍵。

+1

第二部分,我照顧了部分款項,讓我有點拉到一起,但整體上整個解決方案工作。謝謝! – Pharaun 2011-04-02 00:35:44

+0

@Pharaun:感謝您的反饋 - 您是否將SSE代碼與原始標量代碼進行了基準測試,如果有的話,您看到了多少性能提升? – 2011-04-02 09:55:34

+0

@Paul使用單線程實現我敲掉了20%,但通過openMP使用多線程,這只是一個百分比,所以我仍然需要調整它,並且正在考慮轉向浮動以獲得更多性能。 – Pharaun 2011-04-03 00:41:07

32

我建議使用按位和麪具。正值和負值具有相同的表示形式,只有最重要的位不同,正值爲0,負值爲1,請參閱double precision number format。您可以使用下列操作之一:

inline __m128 abs_ps(__m128 x) { 
    static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31 
    return _mm_andnot_ps(sign_mask, x); 
} 

inline __m128d abs_pd(__m128d x) { 
    static const __m128d sign_mask = _mm_set1_pd(-0.); // -0. = 1 << 63 
    return _mm_andnot_pd(sign_mask, x); // !sign_mask & x 
} 

而且,它可能是一個好主意,展開循環來打破循環搬運依存鏈。由於這是非負的值的總和,求和的順序並不重要:

double norm(const double* sima, const double* simb) { 
__m128d* sima_pd = (__m128d*) sima; 
__m128d* simb_pd = (__m128d*) simb; 

__m128d sum1 = _mm_setzero_pd(); 
__m128d sum2 = _mm_setzero_pd(); 
for(int k = 0; k < 3072/2; k+=2) { 
    sum1 += abs_pd(_mm_sub_pd(sima_pd[k], simb_pd[k])); 
    sum2 += abs_pd(_mm_sub_pd(sima_pd[k+1], simb_pd[k+1])); 
} 

__m128d sum = _mm_add_pd(sum1, sum2); 
__m128d hsum = _mm_hadd_pd(sum, sum); 
return *(double*)&hsum; 
} 

通過展開,並打破了依賴(SUM1和SUM2現在是獨立的),你讓處理器執行的我們的訂單的增加。由於該指令在現代CPU上流水線化,所以CPU可以在前一個完成之前開始新的添加。而且,按位操作在單獨的執行單元上執行,CPU實際上可以在與加/減操作相同的週期內執行。我建議Agner Fog's optimization manuals

最後,我不推薦使用openMP。循環太小,在多個線程之間分配作業的開銷可能大於任何潛在的好處。

+0

當我有一段時間的時候,我會看看這個,但我想評論OpenMP的方面......我在這個問題中使用/呈現的代碼片段是一個嵌套循環*另一個巨大的循環,並且我在外部循環中使用了OpenMP,而不是在內部循環中:)但是,如果OpenMP適用於這個較小的內部循環,那麼您會是正確的。 – Pharaun 2011-05-16 18:13:03

+0

你好,我能夠實現這一點,它確實加快了幾個百分比,這是很好:)我真的很喜歡Agner霧的手冊,他們真的很好! – Pharaun 2011-06-23 21:22:51

7

-x和x的最大值應該是abs(x)。這裏是代碼:

x = _mm_max_ps(_mm_sub_ps(_mm_setzero_ps(), x), x) 
+1

是的,當我發佈這個問題時,我並沒有意識到這個技巧,但是它確實有意義:) – Pharaun 2011-05-16 18:01:24