机器代码布局优化

CPU 前端(FE)负责获取和解码指令,并将它们传递给乱序执行的后端。随着新型处理器获得更多的执行"马力(horsepower)",CPU FE 需要足够强大以保持机器平衡。如果 FE 无法跟上指令供应,后端将被低效利用,整体性能将受到影响。这就是为什么 FE 设计为始终领先于实际执行,以平滑可能出现的任何问题(hiccup),并始终准备好待执行的指令。例如,2016年发布的英特尔 Skylake 可以在每个周期内获取多达16条指令。

大多数时候,CPU FE 的效率低下可以描述为后端等待执行指令,但 FE 无法提供它们的情况。结果,CPU 周期在没有进行任何实际有用工作的情况下被浪费。回想一下,现代 CPU 每个周期可以处理多条指令,现在范围从4- 到 8-wide。不是所有可用插槽都被填满的情况经常发生。这代表了数据库、编译器、网络浏览器等多个领域应用程序的效率低下来源。

TMA 方法论通过 前端瓶颈(Front-End Bound) 指标捕捉 FE 性能问题。它代表了 CPU FE 无法向 BE 提供指令的周期百分比,而它本可以接受它们。大多数实际应用程序都会经历非零的Front-End Bound指标,这意味着一定百分比的运行时间将因次优的指令获取和解码而损失。低于10%是常态。如果你看到"Front-End Bound"指标超过20%,那么绝对值得花时间解决它。

FE 无法向执行单元提供指令的原因可能有很多。大多数时候,这是由于代码布局不佳,导致 I-cache 和 ITLB 利用不佳。拥有大型代码库的应用程序,例如数百万行代码,特别容易受到 FE 性能问题的影响。在本章中,我们将探讨一些典型的优化措施,以改善机器代码布局并提高程序的整体性能。

机器代码布局

当编译器将源代码转换为机器代码时,它会生成一个线性的字节序列。@lst:MachineCodeLayout 显示了一个小段 C++ 代码的二进制布局示例。一旦编译器完成生成汇编指令,它需要对它们进行编码并按顺序排列在内存中。

代码清单:机器代码布局示例

  C++ Code        Assembly Listing      Disassembled Machine Code
  ........        ................      ......................... 
if (a <= b)   │    ; a is in edi     │  401125 cmp esi, edi
  bar();      │    ; b is in esi     │  401128 jb 401131
else          │    cmp esi, edi      │  40112a call bar
  baz();      │    jb .label1        │  40112f jmp 401136
              │    call bar()        │  401131 call baz
              │    jmp .label2       │  401136 ...
              │  .label1:            │
              │    call baz()        │
              │  .label2:            │
              │    ...               │

代码在二进制文件中放置的方式称为 机器代码布局。请注意,对于同一个程序,可以以许多不同的方式布局代码。对于 @lst:MachineCodeLayout 中的代码,编译器可能决定反转分支,以便首先调用baz。此外,函数barbaz的主体可以以两种不同的顺序放置:我们可以先在二进制文件中放置bar,然后放置baz,或者反转顺序。这会影响指令在内存中放置的偏移量,进而可能会影响生成的二进制文件的性能,正如您将在后面看到的。在本章的以下部分中,我们将介绍一些机器代码布局的典型优化。

results matching ""

    No results matching ""