为什么编译器优化不生成 1..N 整数之和的循环?

BZK*_*ZKN 14 c++ assembly loops clang compiler-optimization

为了更好地理解编译器,特别是汇编语言,我一直在尝试一段简单的代码,其中N计算第一个数字的总和,这应该导致N(N+1)/2or N(N-1)/2

正如代码所示,有两个函数:

#include <cstdint>


// Once compiled with optimization, the generated assembly has a loop

uint64_t sum1( uint64_t n ) {  
    uint64_t sum = 0;
    for ( uint64_t j=0; j<=n; ++j ) {
        sum += j;
    }
    return sum;
}

// Once compiled with optimization, the generated assembly of the following has no loop

uint64_t sum2( uint64_t n ) {  
    uint64_t sum = 0;
    for ( uint64_t j=0; j<n; ++j ) {
        sum += j;
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

在第一个函数中,我从O 循环到 N ie j<=n,在第二个函数中,我从O 循环到 N-1 ie j<n

我的理解/观察:

  • 对于第一个函数,sum1生成的程序集有一个循环,而对于第二个函数,sum2程序集没有显示循环。但是,一旦我删除了编译器优化-O3,即,您终于可以看到汇编中第二个函数的循环。

  • 要查看经过编译器优化生成的程序集,请参阅此Optimized

  • 要查看生成的未经编译器优化的程序集,请参阅此非优化的.

  • 编译器是x86-64 clang

问题:为什么编译器优化不显示程序集中的其他循环?

Sam*_*hik 16

这是因为你的编译器非常非常聪明,它知道从 0 到 n 的所有值的总和可以用一个简单的数学公式来计算,而不是循环。

然而,您的 C++ 编译器还发现这个数学公式不能在该<=版本中使用,因为对于某些输入值,会触发错误,从而导致无限循环,因此所有的赌注都会被取消,并且编译器会完全按照给定的方式编译代码。

  • @Jellyboy:线性序列和循环的模式识别器很可能正在寻找循环中的特定事物,其中可能包括可证明有限的循环。事实上,clang/LLVM 的 *this* 部分未能使用“__builtin_assume”中的值范围信息,这很容易导致优化失败。尽管有趣的是,如果您执行“n &amp;= 0xfff”而不是假设,优化器就能够使用高斯公式:https://godbolt.org/z/Mn16PYvqo(避免溢出的努力较少,但仍然超过需要小“n”。) (6认同)
  • 确实,在调试代码时,您不希望发生此类优化。这正是优化需要配置设置来启用(或禁用)的原因。 (3认同)
  • @Jellyboy:这是显而易见的,也是迄今为止最有可能的解释。对两个版本的一些实验都与该理论一致,例如“j += 3”使得“j&lt;n”版本无法优化。有趣的是,使用“__builtin_assume(n&lt;1000)”会破坏我们使用“n&amp;=0xffff”获得的优化。https://godbolt.org/z/5j3nhdnvT。无论 `__builtin_assume` 如何,对于 clang 来说,完全空循环的删除*总是*会发生。(GCC 不会这样做,尽管使用 `if(!x)__builtin_unreachable()` 的 ASSUME 确实可以让 GCC 删除潜在的无限空循环。) https://godbolt.org/z/4TKfxq4rn (3认同)
  • 是的,确实如此,我们应该期待“好的”猜测,最好有足够的证据支持该理论。还有有用的实用建议,这就是。期望回答者深入研究 LLVM 源代码并找到确切的模式识别器算法来排除任何其他可能性是不合理的。如果有人知道更准确的解释(或者更糟糕的是,如果解释完全错误,这在基于 CPU/缓存工作方式的性能问题上并不罕见),他们可以发表评论。但缺乏明确的证据在我看来并不是问题,至少在这里是这样。 (3认同)
  • 如果你让无限循环变得更加明显,clang 会识别它并删除它,即使使用了 sum 中计算的值。它甚至会产生一个没有指令的空函数,这会导致无意义的程序:https://godbolt.org/z/cv6ndjYhT/z/Pff5rf1eh (2认同)