Performance 在不销毁两个SSE寄存器的情况下,检查它们是否都不为零
我想测试两个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
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.1ptest
指令
到目前为止,我一直在使用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
。