2014-10-29 21 views
2

我想測試兩個SSE寄存器如果不摧毀它們都不爲零。檢查兩個SSE寄存器是否都不爲零而不破壞它們

這是我目前擁有的代碼:

uint8_t *src; // Assume it is initialized and 16-byte aligned 
__m128i xmm0, xmm1, xmm2; 

xmm0 = _mm_load_si128((__m128i const*)&src[i]); // Need to preserve xmm0 & xmm1 
xmm1 = _mm_load_si128((__m128i const*)&src[i+16]); 
xmm2 = _mm_or_si128(xmm0, xmm1); 
if (!_mm_testz_si128(xmm2, xmm2)) { // Test both are not zero 
} 

這是最好的方式(最多可使用SSE 4.2)?

+0

你還能指望什麼?充其量不用或。 – 2014-10-29 10:24:45

+3

我會使用'_mm_movemask_epi8'而不是'_mm_testz_si128',但實際上'_mm_testz_si128'通常更好。只有Nahalem和Westmere的'_mm_movemask_epi8'具有較低的延遲。但Haswell更糟糕。但更重要的是它不會在FLAGS寄存器中設置零或進位標誌,而是'_mm_testz_si128'。所以你現在擁有的可能是最好的。 – 2014-10-29 19:57:58

+0

其實這是我正在尋找的討論類型。我會將其標記爲答案,但這是一條評論。 – ChipK 2014-10-29 23:31:10

回答

3

我從這個問題中學到了一些有用的東西。讓我們先來看看一些標碼

extern foo2(int x, int y); 
void foo(int x, int y) { 
    if((x || y)!=0) foo2(x,y); 
} 

編譯此類似這樣gcc -O3 -S -masm=intel test.c和重要組件

mov  eax, edi ; edi = x, esi = y -> copy x into eax 
or  eax, esi ; eax = x | y and set zero flag in FLAGS if zero 
jne  .L4  ; jump not zero 

現在讓我們來看看測試SIMD寄存器爲零。與標量代碼不同,沒有SIMD FLAGS寄存器。但是,在SSE4.1中,有SIMD測試指令可以在標量FLAGS寄存器中設置零標誌(和進位標誌)。

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    __m128i z = _mm_or_si128(x,y); 
    if (!_mm_testz_si128(z,z)) foo2(x,y); 
} 

編譯c99 -msse4.1 -O3 -masm=intel -S test_SSE.c和重要的組件是

movdqa  xmm2, xmm0 ; xmm0 = x, xmm1 = y, copy x into xmm2 
por   xmm2, xmm1 ; xmm2 = x | y 
ptest  xmm2, xmm2 ; set zero flag if zero 
jne   .L4  ; jump not zero 

注意,這需要一個指令,因爲包裝的逐位或不設置零標誌。還要注意,標量版本和SIMD版本都需要使用額外的寄存器(在標量情況下爲eax,在SIMD情況下爲xmm2)。 所以要回答你的問題,你現在的解決方案是最好的。

但是,如果您沒有使用SSE4.1或更高版本的處理器,則必須使用_mm_movemask_epi8其中僅需要SSE2另一種替代方法是使用_mm_movemask_epi8

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    if (_mm_movemask_epi8(_mm_or_si128(x,y))) foo2(x,y); 
} 

的重要組件是

movdqa  xmm2, xmm0 
por   xmm2, xmm1 
pmovmskb eax, xmm2 
test  eax, eax 
jne   .L4 

注意,這需要一個指令然後與SSE4.1 ptest指令。

到目前爲止,我一直在使用pmovmaskb指令,因爲前Sandy Bridge處理器上的延遲比ptest更好。但是,我在Haswell之前意識到了這一點。在Haswell上,pmovmaskb的延遲比ptest的延遲更差。它們都具有相同的吞吐量。但在這種情況下,這並不重要。什麼是重要的(我之前沒有意識到)是pmovmaskb沒有設置FLAGS寄存器,所以它需要另一條指令。 所以現在我將在我的關鍵循環中使用ptest謝謝你的問題。

編輯:正如OP所建議的,有一種方法可以在不使用另一個SSE寄存器的情況下完成。

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    if (_mm_movemask_epi8(x) | _mm_movemask_epi8(y)) foo2(x,y);  
} 

從GCC相關組件是:

pmovmskb eax, xmm0 
pmovmskb edx, xmm1 
or   edx, eax 
jne   .L4 

使用另一XMM寄存器此使用兩個標量寄存器代替。

請注意,較少的指令並不一定意味着更好的性能。哪種解決方案最好?你必須測試他們每個人找出。

+0

if(_movemask_epi8(x)| _movemask_epi8(y))?這不會創建兩個movemskb和一個「或」命令 - 共有3個? – ChipK 2014-10-31 15:46:59

+0

@ChipK,你是完全正確的(見我更新的答案)。看起來你回答了你自己的問題。有可能在不使用另一個XMM寄存器的情況下執行此操作。 – 2014-11-01 16:12:12

+2

這個答案中的'pmovmskb'部分是假的。您只測試符號位:結果不取決於任何字節的位0..6! 'por' /'ptest' /'jnz'是一個不錯的選擇。另一種方式是[相同數量的微博](http://stackoverflow.com/questions/7989897/is-an-m128i-variable-zero/7991083#comment59454214_35890766)。 'por' /'pcmpeqb'(全零)/'pmovmskb' /'test/jnz'。 – 2016-03-09 16:17:18

1

如果使用C/C++,則無法控制各個CPU寄存器。如果你想完全控制,你必須使用匯編器。

+1

對,編譯器可以自由分配寄存器。 – 2014-10-29 10:34:08

相關問題