我提供了 4 个代码示例,在 Godbolt 上有一些注意事项。
#include <stdint.h>
#include <xmmintrin.h>
bool Test1(int x, int y, int g, int h, int m, int n, int q, int r) {
return x < y && g < h && m < n && q < r;
}
bool Test2(int x, int y, int g, int h, int m, int n, int q, int r) {
const bool a = x < y && g < h;
const bool b = m < n && q < r;
return a && b;
}
bool TestSIMD(__m128i v1, __m128i v2) {
__m128i vcmp = _mm_cmplt_epi32(v1, v2);
uint16_t mask = _mm_movemask_epi8(vcmp);
return (mask == 0xffff);
}
bool Test4(int x, int y, int g, int h, int m, int n, int q, int r) {
return x < y & g < h & m < n & q < r;
}
编译成
Test1(int, int, int, int, int, int, int, int):
cmp edi, esi
setl al
cmp edx, ecx
setl dl
and al, dl
je .L1
cmp r8d, r9d
mov ecx, DWORD PTR [rsp+16]
setl al
cmp DWORD PTR [rsp+8], ecx
setl dl
and eax, edx
.L1:
ret
Test2(int, int, int, int, int, int, int, int):
cmp r8d, r9d
mov r10d, DWORD PTR [rsp+16]
setl al
cmp DWORD PTR [rsp+8], r10d
setl r8b
and eax, r8d
cmp edx, ecx
setl dl
and eax, edx
cmp edi, esi
setl dl
and eax, edx
ret
TestSIMD(long long __vector(2), long long __vector(2)):
vpcmpgtd xmm1, xmm1, xmm0
vpmovmskb eax, xmm1
cmp ax, -1
sete al
ret
Test4(int, int, int, int, int, int, int, int):
cmp r8d, r9d
mov r10d, DWORD PTR [rsp+16]
setl al
cmp DWORD PTR [rsp+8], r10d
setl r8b
and eax, r8d
cmp edx, ecx
setl dl
and eax, edx
cmp edi, esi
setl dl
and eax, edx
ret
循环时间是近似的,因为我没有费心分析每条指令。
- 第一种情况有条件分支,如果每次都没有正确预测分支,可以是bad。取 2(正确预测及早输出)、3、4 或 2+12(分支错误预测)。由于数据微不足道且没有副作用,因此编译器会随意短路。
- 第二种情况没有分支,但与第一种情况一样需要 3 或 4 个周期。但是每次的执行时间应该是一样的。
- SIMD 解决方案需要 4 个周期,由于数据依赖性,没有分支。但是使用的流水线要少得多,因此可以与更多指令重叠。此外,数据必须可加载到寄存器中或已经存在于寄存器中,这至少需要一个额外的周期。
- & 解决方案也需要 4 个周期,但也使用 13 条指令。
因此,如果这与您的问题很接近,请使用 SIMD,如果您可以在您的平台上使用最快的 Test2 和 Test4。