关注了就能看到更多这么棒的文章哦~
AutoFDO and Propeller
By Jake Edge
October 28, 2024
LPC
Gemini-1.5-flash translation
https://lwn.net/Articles/995397/
Rong Xu 和 Han Shen 在 toolchain track 环节的讨论中,为 2024 年 Linux Plumbers 大会 介绍了谷歌在内核优化方面采用的技术。他们着重介绍了 自动反馈导向优化 (AutoFDO,automatic feedback-directed optimization),该技术可以与 Propeller 优化器结合使用,通过收集真实工作负载的性能信息来构建性能更优的内核。这些工具与 BOLT 后链接优化器有很大的重叠,而 BOLT 是本次会议之前环节的主题。
AutoFDO
Xu 从介绍 AutoFDO 开始,随后 Shen 讲解了在 AutoFDO 基础上使用 Propeller 进一步提升性能的技术。在 Xu 和 Shen 工作的谷歌公司,他们对内核在整个系统中执行所占用的时间进行了测量。Xu 展示了一张幻灯片,其中显示了内核的 CPU 周期百分比(不包括空闲时间),涵盖了各种工作负载,这些信息可以在 幻灯片 或者 YouTube 视频 中找到。内核占用 CPU 周期的百分比在 17% 到 60% 之间,整个系统的平均值约为 20%。
Xu 表示,针对 Linux 内核的优化已经进行了很多努力,在谷歌,主要技术之一是 反馈导向优化 (FDO),该技术利用运行时信息来改进代码生成。其核心思想是收集真实工作负载的性能信息,以便编译器能够根据程序的运行方式,集中精力进行那些影响最大的优化。
Xu 表示,FDO 已被证明是有效的,并且应用于谷歌几乎所有应用中,包括 Android、ChromeOS 和数据中心。它可以提高指令缓存的使用率,减少 TLB 缺失,并提高 branch (分支跳转)性能。总体的运行时改进幅度可达 20%,但 FDO 还带来了另一个好处:它通常可以减少代码大小。

FDO 主要有两种类型:基于插桩的和基于采样的。基于插桩的这种方式(也称为 iFDO)是最初的版本,它使用一个插桩内核来运行代表真实工作负载的测试,从而生成用于优化的性能信息。然后,这些性能信息被传递给编译器,用于构建可以部署到生产环境的优化内核。插桩内核可以提供准确的性能信息,而不依赖于性能监控单元 (PMU) 硬件,但生成的内核速度要慢十倍,因此无法在生产环境中使用。不过,负载测试仍然需要具有代表性。除此之外,另一个缺点是 iFDO 需要对内核进行插桩操作。
基于采样的 FDO (或 AutoFDO) 则从一个带有调试符号的内核开始,然后使用 perf 工具来测量代表性的工作负载。原始的 perf 数据被转换为 AutoFDO 性能信息,然后被传递给编译器,用于构建优化内核。由于基于 perf 的数据收集开销很低,因此测试内核可以在生产环境中使用真实工作负载,消除了对真正具有代表性的测试用例的担忧。但是,AutoFDO 的优化效果不如 iFDO,因为性能信息并不完整,“所以如果你使用相同的负载测试,你将会得到接近的性能,但不会那么好”。此外,AutoFDO 需要硬件 PMU 和 perf 的支持。
对于快速迭代的项目,建议使用一个迭代方式的流程,将收集到的生产环境中运行的内核的性能信息传递给编译器,用于构建下一个版本的测试内核。然后,这个版本在生产环境中运行,收集到的性能信息用于构建下一个版本,以此类推。虽然存在代码不匹配的情况,但 AutoFDO 使用的是相对于函数起始位置的行号;如果版本之间没有进行重大的重构,“我们会发现这种方法运行得很好”。Xu 表示,开发人员认为这种模式适用于内核的次要版本。
AutoFDO 需要处理器支持最后分支记录 (LBR) 或类似的功能;AMD 分支采样 (BRS) 和 Arm 嵌入式跟踪宏单元 (ETM) 可以提供相同类型的信息。有了这些信息,AutoFDO 可以创建一个性能信息,从而允许进行多种类型的优化。通常影响最大的有三种:函数内联、增加分支回退和间接调用提升。
内联有助于减少代码的大小和运行时间。回退 branch 动作比进行 branch 动作更快,即使它被正确预测;此外,这种优化可以将热点基本代码块组合在一起,以提高指令缓存的使用率。最后,间接调用可以提升为直接调用,从而允许它们被内联。其他优化阶段可以查询性能信息,以检索基本块、分支跳转等计数,因此这些优化也会得到改进。
Xu 简要报告了一些性能数据。在几乎所有的测试中(包括微基准测试和工作负载测试),iFDO 的性能略好于 AutoFDO,并且大多数情况下都是有适度提升(2-10%)。在一个谷歌数据库应用程序的工作负载测试中,AutoFDO 的性能提高了 2.6%,而 iFDO 提高了 2.9%。他与一些 Meta 工程师合作,他们报告称,在他们的一个服务中使用 AutoFDO 实现了大约 5% 的性能提升,使用 iFDO 实现了大约 6% 的性能提升。
总的来说,在内核中使用 AutoFDO 的过程相当顺利。“测试被证明是工作中最具挑战性的部分”;寻找具有代表性的工作负载和基准测试比较困难。在开发过程中,他们发现来自英特尔机器的性能相关信息在 AMD 机器上也能很好地用起来。为了获得更好的性能,他建议使用 链接时优化 (LTO) 和 ThinLTO。
Propeller
随后,Shen 上台介绍了使用 Propeller 构建内核的技术。他首先介绍了什么是后链接优化器(post-link optimizer),但他指出,之前的 BOLT 演讲已经很好地介绍了它。后链接优化器只需要二进制文件及其在代表性工作负载上的运行性能信息,通常不需要源代码。它使用反汇编器将机器码转换为编译器中间表示 (IR),然后进行分析和优化,最后将结果转换回机器码作为最终输出。
从概念上来说,Propeller 也是一个后链接优化器,但它采用了一种不同的方法;它使用编译器和链接器来进行转换。与其他后链接优化器一样,它接收二进制文件和性能信息,以识别一系列优化操作,但它不会直接应用这些优化。相反,它会写入编译器和链接器的性能信息,然后使用相同的源代码重新构建。编译器和链接器会执行它们通常进行的那些动作,但会使用性能信息来应用优化操作。最终结果相同,都是一个优化后的二进制文件,但内部步骤不同。

采用这种方法的原因有几个。在进行后链接优化时,开发人员发现反汇编器并不适用于所有二进制文件。例如,它不适用于包含代码和只读数据的二进制文件,也不适用于必须符合各种标准的二进制文件,例如 FIPS 标准。除此之外,还存在可扩展性问题:“后链接优化器的单机多线程设计导致处理时间和内存使用过长”。这促使他们将部分负担转移到编译器和链接器上。
Propeller 机制有一些优点。首先,优化转换“以最精确的方式完成”,因为它们由编译器和链接器处理。它还“为并行化打开了大门,因为每个编译任务都是独立的”。可以利用分布式构建缓存;如果没有为特定源文件找到优化指令,则不需要重新构建该源文件,可以使用缓存的对象文件。这也意味着 Propeller 本身不需要处理内核元数据,这些元数据都是由现有的构建过程处理的。然而,Propeller 需要源代码和重建步骤,这两者都是这种方法的缺点。
目前,Propeller 主要进行了两种优化:基本块布局和路径克隆。Shen 表示,他们还在积极开发跨进程寄存器分配功能。
可以通过构建一个 AutoFDO 性能信息,用于构建一个内核,然后对该内核进行性能信息收集,从而将 Propeller 与 AutoFDO 结合使用。用于构建 AutoFDO 内核的性能信息也会被用于使用 Propeller 性能信息重新构建内核。每个内核都在生产环境中运行,以生成所需的性能信息。最终结果是一个同时被 AutoFDO 和 Propeller 优化的内核。
与其他后链接优化器不同,Propeller 可以优化内核模块。后链接优化器针对可执行文件和共享库进行操作,而内核模块是可重定位对象。Propeller 可以解析模块中的重定位信息,以链接到正确的符号。
如前所述,AutoFDO 可以容忍源代码的微小更改和构建选项的细微差别,但 Propeller 却不行。用于生成性能信息的内核的源代码和构建选项必须与 Propeller 使用的源代码和构建选项完全相同。开发人员“正在努力尝试减轻这种限制”,他说。
Propeller 需要特定的软件和硬件支持才能执行其工作。它依赖于一个位于 LLVM 源代码树之外的 工具。开发人员目前正在将该功能迁移到 LLVM 中。它还需要来自 CPU 的跳转信息,这些信息来自于不同架构下的不同功能(英特尔的 LBR、AMD 的 BRS 或 LBRv2 以及 Arm 的 SPE 或 ETM)。对于内部应用,Propeller 已在每个架构上(虽然不是每个跳转跟踪功能)进行了验证;对于内核,目前只验证了英特尔 LBR。
Shen 展示了一些 PMU 统计数据的热图和图表,涵盖了没有 FDO、iFDO、AutoFDO 以及后两者结合 ThinLTO 和 Propeller 的情况。正如预期的那样,Propeller 在几乎所有类别中都实现了改进。
Shen 表示,目前,在 LLVM 中启用 AutoFDO 和 Propeller 的补丁已经 提交到上游进行审查。在内部,开发人员正在进行“大规模生产测试,以衡量公司范围内的性能”;他们还在针对特定工作负载的客户内核进行研究,以构建针对不同应用程序的不同内核。
总而言之,他说,FDO,包括 iFDO 和 AutoFDO,“显著提高了内核性能”。AutoFDO 与内核构建良好集成,并允许在生产环境中进行性能信息收集,这比 iFDO 具有很大的优势。他们的建议是在 AutoFDO 的基础上添加 Propeller,以获得最佳性能。
在问答环节中,Peter Zijlstra 询问了为什么要进行多次性能信息收集;他说,AutoFDO 性能信息应该已经包含了必要的信息。Shen 说,第一次优化操作会改变代码,导致性能信息过时,而 Propeller 的性能信息收集则可以解决这个问题。Zijlstra 指出,代码在此时会再次进行转换,因此需要另一个性能信息;Shen 同意这是一个迭代过程,更多的性能信息收集和重建步骤会改善结果。
Zijlstra 还抱怨说,将 Propeller 称为后链接优化器并不准确。优化是在构建过程中进行的。另一位观众同意,说性能信息是在后链接阶段收集的,但它只是另一种形式的性能引导优化;Zijlstra 建议应该直接将 Propeller 称为性能引导优化(profile-guided optimization),以避免混淆。
似乎,使用 LLVM 生成优化内核的工作有很多,虽然 BOLT 也可以对使用 GCC 构建的内核进行操作。这项工作是由拥有大量云系统机群的公司完成的,这一点并不令人意外;性能的微小提升乘以机群的大小,就会对 CPU 使用率、功耗、所需的系统数量等产生重大影响,最终都会导致节省资金。
[ 我要感谢 Linux 基金会,他们作为 LWN 的旅行赞助商,为我前往维也纳参加 Linux Plumbers 大会提供了旅行资助。 ]
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

757



被折叠的 条评论
为什么被折叠?



