我从这个问题中学到了一些有用的东西。让我们先来看看一些标码
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寄存器此使用两个标量寄存器代替。
请注意,较少的指令并不一定意味着更好的性能。哪种解决方案最好?你必须测试他们每个人找出。
你还能指望什么?充其量不用或。 – 2014-10-29 10:24:45
我会使用'_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
其实这是我正在寻找的讨论类型。我会将其标记为答案,但这是一条评论。 – ChipK 2014-10-29 23:31:10