编译器是否能够避免分支指令?

时间:2015-08-30 10:00:28

标签: c++ c++11 bit-manipulation c++14 micro-optimization

我一直在阅读有关bit twiddling hacks的内容并认为,编译器是否能够避免在以下代码中进行分支:

constexpr int min(const int lhs, const int rhs) noexcept {
    if (lhs < rhs) {
        return lhs;
    }
    return rhs
}

将其替换为(explanation):

constexpr int min(const int lhs, const int rhs) noexcept {
    return rhs ^ ((lhs ^ rhs) & -(lhs < rhs));
}

3 个答案:

答案 0 :(得分:7)

  • 编译器是否能够...... :肯定是!
  • 我可以依赖这些优化吗?:不,您不能依赖任何优化。可能总会存在一些奇怪的条件,在这些条件下,编译器选择不为某些非显而易见的原因实现某种优化,或者只是看不到这种可能性。另外一般来说,我观察到编译器有时会比人们想象的要笨重(或者人(包括我)比他们想象的要笨)。(1)
  • 没问,但是一个非常重要的方面:我可以依靠这实际上是一个优化吗?不!首先,(特别是在x86上)性能总是取决于周围的代码,并且存在许多相互作用的不同优化。此外,某些体系结构甚至可能提供更有效地实现操作的命令。
  • 我应该使用bit twiddeling优化吗?:一般情况下:不 - 特别是没有验证他们实际上给你带来任何好处!即使它们确实提高了性能,它也会使您的代码更难以阅读和审查,并且它会使一些体系结构和编译器特定的假设(整数的表示,指令的执行时间,分支未命中预测的惩罚...),这可能导致将代码移植到另一个体系结构时性能更差,或者 - 在最坏的情况下甚至会导致错误的结果。

我的建议:
如果您需要获得特定系统的最后一点性能,那么只需尝试两种变体并进行测量(并每次验证结果,更新CPU和/或编译器)。对于任何其他情况,假设编译器至少与您进行低级优化一样好。我还建议您首先了解所有与优化相关的编译器标志,并在开始使用低级优化之前设置适当的基准。

我认为手部优化有时仍然有益的唯一区域是,如果您想要最佳地使用矢量单位。现代编译器可以自动向量化很多东西,但这仍然是一个相对较新的领域,并且有些东西不允许编译器执行,因为它违反了标准的某些保证(特别是涉及浮点运算的地方)。 p>

(1)有些人似乎认为,独立于代码的外观,编译器将始终生成提供相同语义的最佳代码。首先,编译器在有限的时间内可以做什么是有限的(有很多启发式工作最有效,但并非总是如此)。其次,在许多情况下,c ++标准要求编译器提供某些保证,目前您实际上并不感兴趣,但仍然阻止优化。

答案 1 :(得分:4)

clang++(3.5.2-1)似乎足够聪明-O3(我不使用c ++ 11或c ++ 14,constexpr和{ {1}}从源代码中删除):

noexcept

08048760 <_Z3minii>: 8048760: 8b 44 24 08 mov 0x8(%esp),%eax 8048764: 8b 4c 24 04 mov 0x4(%esp),%ecx 8048768: 39 c1 cmp %eax,%ecx 804876a: 0f 4e c1 cmovle %ecx,%eax 804876d: c3 ret (4.9.3)(gcc)改为使用-O3进行分支:

jle

(x86 32位)

08048740 <_Z3minii>: 8048740: 8b 54 24 08 mov 0x8(%esp),%edx 8048744: 8b 44 24 04 mov 0x4(%esp),%eax 8048748: 39 d0 cmp %edx,%eax 804874a: 7e 02 jle 804874e <_Z3minii+0xe> 804874c: 89 d0 mov %edx,%eax 804874e: f3 c3 repz ret (已损坏)是备选方案(来自min2):

gcc

答案 2 :(得分:0)

编译器可以检测此模式并将其替换为您的提议。

但是,clang ++和g ++都不进行此优化,例如参见g++ 5.2.0's assembly output