【问题标题】:C++ Compilation Standard for “&” vs “&&” Expressions?“&”与“&&”表达式的C++编译标准?
【发布时间】:2021-05-23 20:46:58
【问题描述】:

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

如果函数写成:

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

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

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

【问题讨论】:

  • 您是否已将用户定义为operator&amp;&amp;?如果不是,则强制执行短路。从左到右,如果一个失败,其他的不被评估。
  • 您是否进行了测量和分析以检查这是否真的可以通过您程序中的前两个或三个瓶颈?如果不是,那么我的建议是不要担心这种微优化。
  • 只需对它进行排序,以使 && 的最左边最有可能为假。第一个 false 之后的任何内容都不会被评估,因为那时您知道返回值为 false。任何比这更聪明的东西都可能需要依赖于产生这些值的情况。
  • 对于性能相关的问题,profile优化的代码是唯一的方法。否则任何与性能相关的代码修改都是猜测,可能会影响性能……因为你不知道,优化器非常好。

标签: c++ performance compiler-optimization short-circuiting


【解决方案1】:

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

【讨论】:

  • 谢谢,这是一个非常有帮助的回复!不幸的是,数据将在变量之间随机均匀分布,因此预测将无济于事。我很惊讶地看到编译器删除了第一个和第三个分支,因为正如其他评论者所说,短路是强制性的。
  • @gmaggiol 他们是强制性的,但是!!!编译器在许多情况下被允许做“as-if”,因为没有副作用,它可以计算所有。
猜你喜欢
  • 1970-01-01
  • 2011-12-24
  • 2011-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-04
相关资源
最近更新 更多