我想测试两个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)吗?
最佳答案
我从这个问题中学到了一些有用的东西。让我们首先看一些标量代码
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 或更高版本的处理器,则必须使用 另一种仅需要 SSE2 的替代方案是使用 _mm_movemask_epi8
。_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 寄存器,而是使用两个标量寄存器。
请注意,更少的指令并不一定意味着更好的性能。这些解决方案中哪一个最好?您必须对它们中的每一个进行测试才能找到答案。
关于performance - 检查两个 SSE 寄存器是否均不为零而不破坏它们,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26623050/