1

好的,因此出于性能原因,编译器可以自由地重新排序代码片段。让我们假设一些代码片段,直接翻译成没有应用优化的机器代码,如下所示:

machine_instruction_1
machine_instruction_2
machine_instruction_3
machine_instruction_4
machine_instruction_5

但是智能编译器认为原始顺序非常低效并重新排序相同的代码,以便生成的机器指令的新顺序如下:

machine_instruction_5
machine_instruction_4
machine_instruction_3
machine_instruction_2
machine_instruction_1

到目前为止,一切都很好。

这是棘手的部分开始的地方。生成的机器指令将由 cpu 执行,只要保留代码逻辑,cpu 就可以以任何它认为适合性能的方式自由地重新洗牌它们。由于我们正在处理指令重新排序的两个“层”:

  • 第一个,由于编译器优化
  • 第二个,由于cpu乱序执行

是什么使编译时指令重新排序完全相关?cpu 看到的只是一系列原始机器指令,没有迹象表明编译器执行了任何先前的优化。如果 cpu 引入了自己的重新排序“层”,为什么它不会使编译器设置的指令顺序无效?基本上,是什么迫使 cpu 尊重编译器优化?编译时重排序和运行时重排序如何“合作”,后者如何补充前者?

4

1 回答 1

3

在考虑指令执行时,必须尊重的是程序语义。只要遵守这一点,任何排序都是正确的。具体来说,这由“依赖关系”来描述,该依赖关系指示某些指令是否需要相对于程序正确行为的给定顺序。例如考虑以下程序

1 x <= y+3
2 z <= 2*x
3 w = 5*y
4 y = 2*a

指令 1 和 2 是相关的。如果修改了它们的相对顺序,则程序不符合程序员的要求,并且禁止任何重新排序。由于不同的原因,在 y 被 1 和 3 使用之前,不能原样执行 4。存在不同类型的依赖关系,包括在考虑控制流时。

编译器和硬件尝试重新排序程序以提高其效率,同时尊重依赖关系。事实上,他们的行动是互补的。

编译器可以考虑比处理器更大的重组,并使用更复杂的启发式方法来完成。编译器可以对程序有很大的了解,并对大部分代码进行重新排序。理论上,如果没有违反依赖关系并且编译器认为它可以改善程序执行,则可以替换 1000 距离处的指令。它可以完全重新组织代码、展开循环等。相反,处理器具有相对有限的预取指令窗口,可以考虑重新排序,任何重新排列只能涉及关闭指令,并且基于一个周期内可行的简单方法。

但是处理器有很大的优势。它可以进行动态重新排序并响应随机事件。在给定时间,它会根据依赖关系和数据可用性考虑可以执行哪些指令,并相应地重新排序代码。这是基于动态依赖关系的,例如,如果前一个依赖指令的结果由于缓存未命中而不可用,它将执行其他尊重依赖关系的指令。使用相同输入数据连续运行同一程序可能会导致不同的排序,具体取决于分支错误预测、缓存未命中等。

所以编译器和处理器之间没有竞争,而是高效的协作。

于 2019-01-17T22:36:35.353 回答