在不破坏它们的情况下检查两个SSE寄存器是否都不为零

时间:2014-10-29 04:58:11

标签: performance optimization assembly sse simd

我想测试两个 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)吗?

2 个答案:

答案 0 :(得分: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 

请注意,这需要多一条指令,因为压缩的逐位OR不会设置零标志。另请注意,标量版本和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);    
}

海湾合作委员会的相关议会是:

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

这不使用另一个xmm寄存器,而是使用两个标量寄存器。

请注意,较少的指令并不一定意味着更好的性能。哪种解决方案最好?你必须测试每一个才能找到答案。

答案 1 :(得分:1)

如果使用C / C ++,则无法控制各个CPU寄存器。如果要完全控制,则必须使用汇编程序。