2014-10-29 64 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