我建議使用按位和麪具。正值和負值具有相同的表示形式,只有最重要的位不同,正值爲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。循環太小,在多個線程之間分配作業的開銷可能大於任何潛在的好處。
第二部分,我照顧了部分款項,讓我有點拉到一起,但整體上整個解決方案工作。謝謝! – Pharaun 2011-04-02 00:35:44
@Pharaun:感謝您的反饋 - 您是否將SSE代碼與原始標量代碼進行了基準測試,如果有的話,您看到了多少性能提升? – 2011-04-02 09:55:34
@Paul使用單線程實現我敲掉了20%,但通過openMP使用多線程,這只是一個百分比,所以我仍然需要調整它,並且正在考慮轉向浮動以獲得更多性能。 – Pharaun 2011-04-03 00:41:07