【问题标题】:Puzzling compiler optimisation result令人费解的编译器优化结果
【发布时间】:2015-04-17 05:41:28
【问题描述】:

如果我尝试运行这样的循环:

int i;

for (i = 0; i < 1e9; i++)
{
    1 + 1;
}

编译器完全优化它,甚至不运行它。但是,如果我将int i 设为静态,那么即使我进行了更高的迭代,它也会继续运行循环。这是在 Visual Studio 2013 中,在发布模式下开启了优化。

【问题讨论】:

  • JavaScript 没有 int 数据类型。事实上,当您在 JavaScript 中声明变量时,您使用 var 声明它们,而不是使用数据类型。 JavaScript 中的变量没有类型。此外,JavaScript 没有被编译。它被解释了。
  • 您使用哪种语言?哪个编译器?哪个平台?
  • 了解周围的上下文对于优化也很重要。你能发布整个函数吗?
  • 不要在整数代码中使用像 1e9 这样的浮点常量,如果您查看生成的程序集,即使您不使用它也会产生大量转换和比较。改为使用 1000000000
  • 这就是全部,它在 main 函数中。这是使用 Visual Studio 2013,优化已打开,正如我所说,将变量设为静态会使编译器不对其进行优化。 @Lưu Vĩnh Phúc,我不知道 1e9 是浮点数,我这样做是因为它看起来更干净。我还在学习。

标签: c++ optimization compiler-optimization


【解决方案1】:

很明显,循环体是一个空操作。循环的唯一作用是更改i 的值。当i 具有自动存储时,编译器可以证明循环后永远不会读取i 的值。因此,整个循环没有任何作用,可以丢弃。

但是,当istatic 时,它的生命周期会超出函数的单次调用。所以i的值是副作用,不能丢弃循环。

您可以争辩说编译器可以更深入地挖掘并证明即使是 static i 也不会被读取,但要做到这一点要困难得多。

【讨论】:

  • 我明白你的意思,但在这种情况下,int 在主循环内被声明为静态,而不是在全局空间之外。感谢你的回答。我想这与你所说的有关。
  • 好收获。正确的词是一生。该变量具有局部作用域,因为它只能从函数内部访问,但它的生命周期是程序的持续时间,而不是单个函数调用的持续时间。
  • 假设 i 在函数的其他任何地方都没有被访问,编译器可能足够聪明,可以看到整段代码没有任何效果。无论如何,如果它想尊重变量i,它应该将循环转换为i= 1e9;,这是一个不平凡的转换。
  • 操作词是prove。编译器必须非常保守地更改代码。发出不正确的代码是编译器的一大罪过。此外,这对于优化来说是一个相对不重要的情况,因为无论如何都应该在性能关键代码中避免使用静态和全局变量。
  • 你坚持什么?如果i 是静态的,那么它的值将在下一次调用该函数时被读取,因此循环确实有效果,无法消除。就像我上面所说的,如果你正在编写性能关键代码,那么你应该避免静态。您希望避免 i 每次都被创建的推理是不正确的,因为您限制了优化器的选项。
【解决方案2】:

当 i 不是静态的时,这整段代码不会影响程序的可观察状态。正如您所注意到的,编译器优化了循环。这称为死代码消除。

现在当 i 为静态时,VC++ 编译器立即放弃并且不优化代码(无论是否适用,大多数重要的优化都不会被使用)。如果两个或更多线程同时调用该函数,那么大多数优化(包括死代码消除)可能会对程序状态产生可观察到的副作用,从而使其非法。

当然,在这种情况下,我们没有使用任何线程。但是 VC++ 不会分析代码(有充分的理由),所以为了安全起见,它就放弃了。

如果它确实分析了代码并发现只有一个线程在调用该函数,那么如果 i 是静态的,它实际上可以通过用加法语句替换循环来优化它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2014-08-29
    相关资源
    最近更新 更多