由于优化而重新排序的代码

Code reordering due to optimization

我听说过很多次优化器可能会重新排序您的代码,我开始相信了。 是否有任何可能发生这种情况的示例或典型案例,我该如何避免这种情况(例如,我希望基准不受此影响)?

大多数时候,只有在程序的可观察效果相同的情况下才允许重新排序 - 这意味着您不应该能够分辨出来。

确实存在反例,例如操作数的顺序未指定,优化器可以自由地重新排列事物。您无法预测这两个函数调用的顺序,例如:

int a = foo() + bar();

继续阅读 sequence points 以了解做出了哪些保证。

有很多不同类型的 "code-motion"(移动代码),这是由优化过程的许多不同部分引起的:

  • 移动这些指令,因为等待内存读取完成而不在内存读取和使用我们从内存中获得的内容的操作之间放置至少一两个指令是浪费时间
  • 将事情移出循环,因为它只需要发生一次(如果你调用 x = sin(y) 一次或 1000 次而不改变 y,x 将具有相同的值,所以没有意义在循环中执行此操作。因此编译器将其移出。
  • 根据 "compiler expects this code to hit more often than the other bit, so better cache-hit ratio if we do it this way" 移动代码 - 例如错误处理被从错误源移开,因为你不太可能得到错误 [编译器通常理解常用函数并且它们通常会导致成功]。
  • 内联 - 代码从实际函数移动到调用函数中。这通常会导致其他影响,例如从堆栈中减少 pushing/poping 寄存器,并且参数可以保留在原处,而不必将它们移动到 "right place for arguments".

我确定我错过了上面列表中的一些案例,但这肯定是最常见的一些。

编译器完全有权这样做,只要它没有任何 "observable difference"(除了 运行 所需的时间和使用的指令数 -这些 "don't count" 在编译器方面存在明显差异)

要避免编译器对您的代码重新排序,您几乎无能为力 - 您可以编写在某种程度上确保顺序的代码。例如,我们可以有这样的代码:

{
int sum = 0;
for(i = 0; i < large_number; i++)
  sum += i;
}

现在,由于未使用 sum,编译器可以将其删除。根据编译器,添加一些检查打印总和的代码将确保它是 "used"。

同样:

 for(i = 0; i < large_number; i++)
 {
     do_stuff();
 }

如果编译器发现 do_stuff 实际上并没有改变任何全局值,或者类似的,它会四处移动代码以形成这样的:

 do_stuff();
 for(i = 0; i < large_number; i++)
 {
 }

编译器也可能会删除——事实上几乎肯定会——现在的空循环,这样它就根本不存在了。 [如评论中所述:如果 do_stuff 实际上并没有改变自身以外的任何东西,它也可能被删除,但我想到的例子是 do_stuff 产生结果的地方,但结果是每次都一样]

(例如,如果您删除 Dhrystone 基准测试中的结果打印输出,就会发生上述情况,因为某些循环计算的值除了打印输出外从未使用过 - 这可能导致基准测试结果超过最高处理器的理论吞吐量增加了 10 倍左右——因为基准测试假设循环所需的指令确实存在,并表示每次迭代执行 X 次标称操作)

除了确保 do_stuff 更新函数外的一些变量,或者 returns 一个 "used" 的值之外,没有简单的方法来确保这种情况不会发生。 (例如总结,或其他)。

removing/omitting 代码的另一个示例是您多次将值重复存储到同一个变量:

int x;

for(i = 0; i < large_number; i++)
    x = i * i;

可以替换为:

x = (large_number-1) * (large_number-1);

有时,您可以使用 volatile 来确保某些事情确实发生了,但在基准测试中,这可能是有害的,因为编译器也无法优化它应该优化的代码(如果您不是请注意您的使用方式 volatile)。

如果您有一些您特别关心的特定代码,最好 [=7​​4=] 它(并用几个最先进的编译器编译它,看看它们实际用它做什么)。

[请注意,移动代码一般来说绝对不是一件坏事 - 我确实想要我的编译器(无论是我自己编写的编译器,还是我正在使用的由其他人编写的编译器) 通过移动代码进行优化,因为只要它正确地这样做,它就会通过这样做产生 faster/better 代码!]