“&”与“&&”表达式的 C++ 编译标准?
C++ Compilation Standard for “&” vs “&&” Expressions?
我有一个核心函数,用于评估与 return 布尔值的 4+ 个简单算术比较。这将在一个非常大的循环中调用 O(N^2) 次,具有基于 return.
的单个条件分支
如果函数写成:
return x < y && g < h && m < n && q < r;
与使用“&”的0相比,它会产生3个条件分支吗?此代码将公开发布,因此可以在具有许多不同编译器实现的许多不同平台上编译。
虽然单个实现可能足以优化短路,但是否将类似的内容写入标准(c++11、14、17 或 20)?仅使用“&”对性能来说是否“更安全”?
我已经提供了 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。
我有一个核心函数,用于评估与 return 布尔值的 4+ 个简单算术比较。这将在一个非常大的循环中调用 O(N^2) 次,具有基于 return.
的单个条件分支如果函数写成:
return x < y && g < h && m < n && q < r;
与使用“&”的0相比,它会产生3个条件分支吗?此代码将公开发布,因此可以在具有许多不同编译器实现的许多不同平台上编译。
虽然单个实现可能足以优化短路,但是否将类似的内容写入标准(c++11、14、17 或 20)?仅使用“&”对性能来说是否“更安全”?
我已经提供了 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。