案例研究:测量代码足迹

正如我们在本章中多次提到的,代码布局优化对具有大量代码的应用程序影响最大。阐明程序中热代码大小不确定的最佳方法是测量其 代码足迹,我们将其定义为程序执行期间触及的机器指令的字节/缓存行/页面数量。

大型代码足迹本身并不一定会对性能产生负面影响。代码足迹不是决定性的指标,它不会立即告诉您是否存在问题。尽管如此,它已被证明作为性能分析中一个有用的额外数据点。结合 TMA 的“前端瓶颈”、“L1 指令缓存未命中率”和其他指标,它可能会加强投资时间优化应用程序机器代码布局的论据。

目前,可以可靠地测量代码足迹的工具还很少。在本案例研究中,我们将展示 perf-tools: https://github.com/aayasin/perf-tools1 一个基于 Linux perf 构建的开源性能分析工具集。为了估计2 代码足迹,perf-tools 利用了英特尔的 LBR(参见 [@sec:lbr]),因此目前它不能在 AMD 或 ARM 架构的系统上工作。下面是一个收集代码足迹数据的示例命令:

$ perf-tools/do.py profile --profile-mask 100 -a <your benchmark>

其中,--profile-mask 100 启动 LBR 采样,-a 允许您指定要运行的程序。此命令将收集代码足迹以及其他各种数据。我们不显示工具的输出,好奇的读者可以自行尝试。

我们选取了一组四个基准测试:Clang C++ 编译、Blender 光线追踪、Cloverleaf 流体动力学和 Stockfish 国际象棋引擎;这些工作负载您应该已经从 [@sec:PerfMetricsCaseStudy] 分析其性能特征的地方熟悉了。我们在基于英特尔 Alderlake 的处理器上使用与 [@sec:PerfMetricsCaseStudy] 中相同的命令运行它们。正如预期的那样,在基于 Skylake 的机器上运行相同基准测试获得的代码足迹数字与 Alderlake 运行的结果非常相似。代码足迹取决于程序和输入数据,而不是特定机器的特性,因此结果在不同架构上应该看起来相似。

四个基准测试的结果分别列在表@tbl:code_footprint](#code_footprint) 中。二进制和 .text 大小是使用标准的 Linux readelf 工具获得的,而其他指标是使用 perf-tools 收集的。如果一个指令内存位置被命中至少一次,该工具将其视为“非冷”,因此,“非冷代码足迹 [KB]”是程序触及的包含机器指令的千字节数。指标“非冷代码 4KB 页”告诉我们程序触及的包含机器指令的非冷 4KB 页数。它们一起帮助我们理解这些非冷内存位置的密度或稀疏性。一旦我们深入研究这些数字,这一点就会变得清晰。最后,我们还提供了前端瓶颈百分比,这是一个您应该已经从 [@sec:TMA] 中了解的关于 TMA 的指标。

指标 Clang17 编译 Blender CloverLeaf Stockfish
二进制大小 [KB] 113,844 223,914 672 39,583
.text 大小 [KB] 67,309 133,009 598 238
非冷代码足迹 [KB] 5,042 313 104 99
非冷代码 4KB 页 6,614 546 104 61
前端瓶颈,Alderlake-P [%] 52.3 29.4 5.3 25.8

表:案例研究中使用的基准测试的代码足迹。

解释:

  • 二进制大小(Binary size): 指的是应用程序的可执行文件大小。
  • .text 大小: 指的是可执行文件中代码段的大小。
  • 非冷代码足迹(non-cold code footprint): 指的是程序执行过程中触及的代码部分的大小,以千字节为单位。
  • 非冷代码 4KB 页(non-cold code 4KB-pages): 指的是非冷代码占用的 4KB 页数。
  • 前端瓶颈(Frontend Bound) Alderlake-P: 指的是由于等待指令而导致的性能瓶颈,以百分比表示。

观察:

  • Clang17 编译的非冷代码足迹最大,其次是 Stockfish 和 CloverLeaf。
  • Blender 虽然 .text 大小很大,但其非冷代码足迹相对较小。
  • CloverLeaf 的非冷代码 4KB 页利用率最高,其次是 Stockfish 和 Clang17。
  • Clang17 的前端瓶颈问题最为严重,其次是 Stockfish 和 Blender。

分析:

  • 代码足迹可以帮助我们了解应用程序对 CPU 前端的压力。
  • 非冷代码 4KB 页利用率可以反映代码布局的紧凑程度。
  • 前端瓶颈指标可以帮助我们识别性能瓶颈。

注意:

  • 此分析不考虑应用程序外部的代码,例如动态链接库。

让我们先来看看二进制和.text的大小。与 Clang17 和 Blender 相比,CloverLeaf 是一个非常小的应用程序;Stockfish 嵌入的神经网络文件占了大部分二进制文件,但其代码部分相对较小;Clang17 和 Blender 拥有庞大的代码库。.text size 指标是我们的应用程序的上限,即我们假设3 代码足迹不应超过 .text 大小。

通过分析代码足迹数据,我们可以做出一些有趣的观察。首先,尽管 Blender 的 .text 部分非常大,但不到 1% 的 Blender 代码是非冷的:133 MB 中只有 313 KB。因此,仅仅因为二进制文件很大,并不意味着应用程序会遭受 CPU 前端瓶颈。真正重要的是热代码的数量。对于其他基准测试,这个比率更高:Clang17 7.5%,CloverLeaf 17.4%,Stockfish 41.6%。从绝对数字来看,Clang17 编译触及的机器指令字节数比其他三个应用程序高出一个数量级。

其次,让我们检查表中的“非冷代码 4KB 页”行。对于 Clang17,5042 KB 的非冷代码分布在 6614 个 4KB 页面上,这给我们提供了 5042 / (6614 * 4) = 19% 的页面利用率。这个指标告诉我们代码的热点部分的密度/稀疏性。每个热点缓存行越靠近另一个热点缓存行,就需要更少的页面来存储热点代码。页面利用率越高越好。本章前面讨论的基本块放置和函数重新排序是提高页面利用率的转换的完美例子。对于其他基准测试,比率为:Blender 14%,CloverLeaf 25%,Stockfish 41%。

现在我们已经量化了四个应用程序的代码足迹,可能会考虑 L1 指令缓存和 L2 缓存的大小以及热代码是否适合。在我们的 Alderlake 处理器上,L1-I 缓存只有 32 KB,不足以完全覆盖我们分析过的任何基准测试。但请记住,在本节开头我们说过,大型代码足迹并不直接指向问题。是的,大型代码库会给 CPU 前端带来更大的压力,但指令访问模式也对性能至关重要。与数据访问相同的局部性原则也适用。这就是为什么我们将其与 Topdown 分析中的前端瓶颈指标结合在一起。

对于 Clang17,5 MB 的非冷代码导致了一个巨大的 52.3% 的前端瓶颈性能瓶颈:超过一半的周期都在等待指令。在所有呈现的基准测试中,它从 PGO 类型优化中获益最多。CloverLeaf 不存在指令获取效率低下的问题;它 75% 的分支都是向后跳转,这表明它们可能是反复执行的相对较小的循环。Stockfish 虽然与 CloverLeaf 具有大致相同的非冷代码足迹,但却对 CPU 前端提出了更大的挑战 (25.8%)。它有更多的间接跳转和函数调用。最后,Blender 比 Stockfish 具有更多的间接跳转和调用。由于进一步的调查超出了本案例研究的范围,因此我们在此停止分析。对于有兴趣继续分析的读者,我们建议根据 TMA 方法深入研究前端瓶颈类别,并查看诸如 ICache_Misses, ITLB_Misses, DSB coverage 等指标。

另一个研究代码足迹的有用工具是 llvm-bolt-heatmap: https://github.com/llvm/llvm-project/blob/main/bolt/docs/Heatmaps.md4,它是 llvm 的 BOLT 项目的一部分。该工具可以生成代码热图,让您细粒度地了解应用程序的代码布局。其主要用途是 1) 评估原始代码布局是否紧凑以及是否可以优化,2) 确保优化后的代码布局在实际负载下仍然紧凑。

1. perf-tools - https://github.com/aayasin/perf-tools
2. perf-tools收集的代码足迹数据是不精确的,因为它是基于LBR记录的采样。不幸的是,其他工具,如英特尔的sde -footprint,不提供代码占用。然而,自己编写一个基于pin的工具来测量准确的代码占用并不难。
3. 这并不总是正确的:一个应用程序本身可能很小,但需要调用多个其他动态链接的库,或者它可能大量使用内核代码。
4. llvm-bolt-heatmap - https://github.com/llvm/llvm-project/blob/main/bolt/docs/Heatmaps.md

results matching ""

    No results matching ""