Performance 在不销毁两个SSE寄存器的情况下,检查它们是否都不为零

Performance 在不销毁两个SSE寄存器的情况下,检查它们是否都不为零,performance,optimization,assembly,sse,simd,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 c

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

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

我从这个问题中学到了一些有用的东西。让我们首先看一些标量代码

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标志寄存器。但是,对于SSE4.1,有一些SIMD测试指令可以在标量标志寄存器中设置零标志(和进位标志)

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);
}
extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
    if (_mm_movemask_epi8(x) | _mm_movemask_epi8(y)) 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 
movdqa      xmm2, xmm0
por         xmm2, xmm1
pmovmskb    eax, xmm2
test        eax, eax
jne         .L4
请注意,这需要一条以上的指令,因为压缩位或未设置零标志。还要注意,标量版本和SIMD版本都需要使用额外的寄存器(标量情况下为
eax
,SIMD情况下为
xmm2
因此,要回答您的问题,您当前的解决方案是最好的。

但是,如果没有SSE4.1或更高版本的处理器,则必须使用
\u mm\u movemask\u epi8
。另一种只需要SSE2的替代方法是使用
\u mm\u movemask\u 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 ; 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 
movdqa      xmm2, xmm0
por         xmm2, xmm1
pmovmskb    eax, xmm2
test        eax, eax
jne         .L4
请注意,这还需要一条指令,然后使用SSE4.1
ptest
指令

到目前为止,我一直在使用
pmovmaskb
指令,因为在Sandy Bridge之前的处理器上,延迟比使用
ptest
要好。然而,我在哈斯韦尔之前就意识到了这一点。在Haswell上,
pmovmaskb
的延迟比
ptest
的延迟差。它们都具有相同的吞吐量。但在这种情况下,这并不重要。重要的是(
pmovmaskb
没有设置标志寄存器,因此它需要另一条指令)现在我将在关键循环中使用
ptest
谢谢你的提问

编辑:根据OP的建议,有一种方法可以在不使用另一个SSE寄存器的情况下进行编辑

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);
}
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寄存器,而是使用两个标量寄存器


请注意,指令越少并不一定意味着性能越好。以下哪种解决方案最好?您必须对每一个进行测试以找出答案。

您希望有什么更好的方法?最好是保留OR。我会使用
\u mm\u movemask\u epi8
而不是
\u mm\u testz\u si128
,但实际上
\u mm\u testz\u si128
通常更好<代码>\u mm\u movemask\u epi8
仅在Nahalem和Westmile上具有较低的延迟。但哈斯韦尔的情况更糟。但更重要的是,它没有在标志寄存器中设置零或进位标志,而是
\u mm\u testz\u si128
设置。所以你现在所做的可能是最好的。实际上,这就是我想要的讨论类型。我会把它标记为答案,但这是一个评论。我想知道为什么这个问题有两张反对票?对我来说,这是一个相当不错的问题。我想知道PTEST是否可以对两个不同的未知操作数做一些有用的事情。(TL:DR,不,你必须把你的数据降到一个寄存器中,对这类问题也是如此)。对,编译器可以自由分配寄存器。如果(_movemask_epi8(x)| _movemask_epi8(y))呢?这不会创建两个movemskb和一个“or”命令吗?总共3个?@ChipK,你完全正确(见我更新的答案)。看起来你回答了自己的问题。不需要使用另一个XMM寄存器就可以做到这一点。这个答案的
pmovmskb
部分是假的。您只测试符号位:结果不依赖于任何字节的0..6位
por
/
ptest
/
jnz
是一个不错的选择。另一种方式是
por
/
pcmpeqb
(对零)/
pmovskb
/
test/jnz