首页 > 解决方案 > “&”与“&&”表达式的C++编译标准?

问题描述

我有一个核心函数评估 4 个以上的简单算术比较以返回一个布尔值。这将在一个非常大的循环中被称为 O(N^2) 次,并且基于返回的单个条件分支。

如果函数写成:

    return x < y && g < h && m < n && q < r;

与使用“&”的 0 相比,它会生成 3 个条件分支吗?此代码将公开发布,因此可以在具有许多不同编译器实现的许多不同平台上编译。

虽然单个实现可能足够聪明以优化短路,但是否将类似的内容写入标准(c++11、14、17 或 20)?只使用“&”对性能来说会“更安全”吗?

标签: c++performancecompiler-optimizationshort-circuiting

解决方案


我已经提供了 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

循环时间是近似的,因为我没有费心分析每条指令。

  • 第一种情况有一个条件分支,如果每次都没有正确预测分支,这可能会很糟糕。取 2(正确预测及早输出)、3、4 或 2+12(分支错误预测)。由于数据微不足道且没有副作用,因此编译器会随意使用短路。
  • 第二种情况没有分支,但与第一种情况一样需要 3 或 4 个周期。但是每次的执行时间应该是一样的。
  • 由于数据依赖性,SIMD 解决方案需要 4 个周期,没有分支。但是使用的流水线要少得多,因此可以与更多指令重叠。此外,数据必须可加载到寄存器中或已经存在于寄存器中,这至少需要一个额外的周期。
  • & 解决方案也需要 4 个周期,但也使用 13 条指令。

因此,如果这接近您的问题,请使用 SIMD,如果您可以在您的平台上使用最快的 Test2 和 Test4。


推荐阅读