RFC-0247:在 Fuchsia 中启用 LTO

RFC-0247:在 Fuchsia 中启用 LTO
状态已接受
领域
  • 构建
  • 工具链
说明

仅在 Fuchsia 中为 Clang 工具链生成的目标二进制文件启用名为链接时优化 (LTO) 的编译器功能,但非调试(也称为发布)编译中的内核除外。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-03-29
审核日期(年-月-日)2024-05-02

总结

我们提议在 Fuchsia 中仅为 Clang 工具链生成的目标二进制文件启用一项名为链接时优化 (LTO) 的编译器功能,但非调试(也称为发布)build 中的内核除外。在此 RFC 中,我们不建议在内核和 Rust 中启用 LTO。

动机

LTO 可实现更好的运行时性能并缩减代码大小,但代价会增加构建时间。LTO 是实现以下目标的第一步,旨在打造更高效、更安全的系统:

将 LTO 与 PGO 结合使用通常可以提高运行时性能,因为 LTO 可以根据收集的配置文件做出引导式决策。此外,LTO 还可以使用一种名为 CFI 的安全缓解技术。在 Fuchsia 上启用 LTO 和 CFI 有助于实现与其他操作系统供应商(如 Android 和 ChromeOS)相当的功能。

利益相关方

教员

hjfreyer@google.com

审核者

  • aaronwood@google.com
  • olivernewman@google.com
  • phosek@google.com

咨询人员

  • awolter@google.com
  • davidroth@google.com
  • fmeawad@google.com
  • mseaborn@google.com

社交

我们与 EngProd、性能、发布、软件汇编、工具链和 Zircon 内核团队社交了此 RFC 的一个版本。

设计

我们发布了 CL,以做好准备,在非调试(也称为发布)build 中默认启用 LTO。我们只需在后续 CL 中启用此功能即可。开发者可以使用 GN 参数完全停用 LTO,以减轻构建时间的影响。

实现

我们实现了此 RFC 中的方案,并实施了必要的更改。

性能

LTO 具有大小和性能优势:

  • 大小优势:我们在 CQ 中运行大小检查工具构建器来衡量 LTO 的大小优势,并发现,仅在 Clang 生成的二进制文件中启用 LTO 可将总代码大小分别在智能显示屏和 core_size_limits.arm64 中减少 0.24 MiB。

  • 性能优势:我们在 CQ 中运行了性能构建工具来衡量 LTO 的性能优势。LTO 改进了智能显示屏中的 405 项测试中的 23 项,我们发现这些测试有很大的提升,其中一些测试超过了 10%。此外,LTO 还在平台构建器中的 5,226 项测试中提升了 1,959 项。我们还发现,平台构建器的性能大幅提升,其中一些有超过 30% 的提升。

向后兼容性

您无需担心向后兼容性。

安全注意事项

已通过 https://fxbug.dev/317396428 完成安全审核。

隐私注意事项

已通过 https://fxbug.dev/314790650 完成隐私权审核。

测试

一旦在 ToT 上默认启用 LTO 并标记任何功能或性能问题,我们计划依赖于自动化测试。

文档

需要将 LTO 添加到 Fuchsia 版本说明中。

缺点、替代方案和问题

编译器在编译单个模块时的优化机会有限,而 LTO 通过在链接期间执行优化来将优化范围扩展到整个程序。这会增加链接时间,通过全程序分析和跨模块优化实现更好的运行时性能。其缺点是,LTO 会将更多的构建成本转移到链接时,而且链接成本更高,从并行和缓存中受益却更少。

缺点:构建时间增加

启用 LTO 会增加 CI/CQ 发布 build 和开发者 build 中的构建时间。我们通过应用此 CL(无论是否使用 RBE/Goma)来衡量构建时间对 CQ 中整洁 build 的影响,并在下面的 ninja 步骤中以 MM:SS 格式报告所用时间。

CQ 构建器 RBE 无 LTO LTO 时间变化
core_size_limits.x64-release 不接受 RBE/戈马 39:00 41:00 +2:00
core_size_limits.x64-release RBE 6:00 8:18 +2:18
core.x64 版本 不接受 RBE/戈马 66:00 66:00 +0:00
core.x64 版本 RBE 20:15 26:00 +5:45
minimum.x64-release 不接受 RBE/戈马 72:00 78:00 +6:00
minimum.x64-release RBE 17:48 25:00 +7:12
workbench_eng.x64-release 不接受 RBE/戈马 41:00 43:00 +2:00
workbench_eng.x64-release RBE 6:30 9:18 +2:48

在下表中,我们通过运行 NINJA_STATUS=["%es] " fx build 显示了开发者构建时间对仅限 build 的 core.x64-release 配置的完整干净 build 的影响。

RBE 无 LTO LTO 时间变化
不接受 RBE/戈马 26:48 27:15 +0:27
RBE 冷缓存 13:58 17:52 +3:54
RBE 温缓存 13:55 15:53 +1:58

我们测量了名为 driver_manager 的单个二进制文件的增量构建时间开销。我们执行了以下步骤来衡量 LTO 对整个构建流水线的影响。

  • 运行 fx build driver_manager 以执行干净构建。
  • 删除此源文件中的此行。
  • 执行增量构建,并通过 NINJA_STATUS=["%es] " fx build driver_manager 测量所用时间。

下表列出了 driver_manager 的增量构建时间开销:

RBE 无 LTO LTO 时间变化
不接受 RBE/戈马 0:43 1:13 +0:30
RBE 1:08 1:22 +0:14

未知:显示潜在问题

LTO 可能会使源代码中的潜在问题暴露出来,因为它会跨模块执行优化,这可能会使某些已经存在的假设失效或发现隐藏的 bug。

未知:对可调试性的影响

还有一些人担心 LTO 对代码生成和调试的影响。虽然 LTO 总体上应该不会对可调试性产生影响,并且我们尚未确定具体情况,但这一点仍不为人知。如果提供了此类情况,我们可以调查编译器调试信息和调试工具在特定情况下是否与 LTO 交互不良。

未来工作:在内核中启用 LTO

我们计划研究在内核中启用 LTO 以评估其优势,并尝试将 LTO 与其他优化(如 PGO)结合使用,然后在内核中启用 LTO。我们决定区分在用户空间和内核中启用 LTO 的好处,并让内核不在此 RFC 的范围内。

未来工作:在 Rust 中启用 LTO

我们还计划在 Rust 中启用 LTO。我们的初步结果表明,在 Rust 中启用 LTO 可以进一步提高应用大小和性能。不过,我们决定分阶段实施,即在不同阶段启用 LTO,以尽可能降低构建时间影响并降低发布风险。

未来工作:启用 FatLTO 和统一 LTO

我们计划研究在 Fuchsia 中启用 FatLTO 和统一 LTO。不同的目标可能有不同的构建时间和性能要求。例如,对某些目标应用 LTO 可能有好处,但对测试等其他目标没有好处。FatLTO 和 Unified LTO 可能有助于降低构建时间开销并降低复杂性。不过,它们尚未准备好全面发布,需要一些调试和测试工作。

之前的图片和参考资料

从 Android 9 开始,Android 在其内核和其他组件中一直启用了 LTO 和 CFI。Chrome 已部署 LTOPGOCFI

我们目前在 Fuchsia 中广泛使用 LTO,具体而言,我们在 Fuchsia 开发中使用的大多数主机工具(例如 ffx)都是默认使用 LTO 构建的,而 LTO 对于提升这些工具的性能至关重要。

FEC 决定

Fuchsia 工程委员会 (FEC) 已投票接受此 RFC,但有一些注意事项。

首先,鉴于 gulfem@google.com 和 phosek@google.com 在 RFC 文本和评论线程中提出的各种原因,FEC 同意,LTO 代表我们向合作伙伴(以及最终用户)提供的二进制文件朝着正确方向迈出的一步。

其次,FEC 承认此项变更的影响具有不确定性。 为发布 build 启用 LTO 会对利益相关方产生正面和负面影响:提升性能会对最终用户带来积极影响;平台开发者会发现经过优化的代码更难以调试;开发者和产品所有者会受益于较小的二进制文件,尤其是在存储限制紧张的设备上;平台开发者的构建时间会更长;基础架构将在编译上花费更多 CPU 周期,但对整体测试执行是否合理有异议,甚至会减少对整体影响的意见。等等。鉴于这种不确定性,我们认为工具链团队已进行了尽职调查。

一旦变更生效,我们鼓励任何认为自己受到了严重负面影响的利益相关方将他们的顾虑上报给工具链团队,如果他们的顾虑得不到解决,请上报给 FEC。每当有新证据出现时,我们可以随时重新评估这个决定。

最后,我们要特别感谢一组受到负面影响的利益相关方,特别是在正常开发工作流中,以发布模式进行构建且位于内核之外的 C++ 平台开发者。此变更很可能会显著减慢增量构建时间。遗憾的是,我们认为启用 LTO 仍然是正确的选择。

然而,这确实引发了一个更大的担忧,我们对此也表示了共同的担忧:随着我们在更多代码库上部署更智能的优化器,受影响的开发者数量将会不断增加,运行速度也会放慢。开发者不太可能知道其构建速度变慢的确切原因,甚至可能没有意识地注意到速度减慢,但他们的生产力却会受损。

为了避免这种情况,我们要求对与工具链相关的其他更改延期,以延长构建时间,直到我们可以将这些更改应用到我们发布的工件而不影响团队中的大量开发者。不过,团队中的很多开发者无法使用调试 build。例如,我们可以通过将目前的两种主要编译模式(调试和发布)变成三种(或许是镜像 Bazel 的“debug、opt 和 Fastbuild”编译模式)来解锁进一步的更改。开销大的优化仅会在其中一种模式下运行,另外两种模式将覆盖绝大多数开发者用例。作为反例,仅记录一组可供各个开发者选择采用的特定低级别编译器标志是不够的,因为这些单独的标志集很难发现,缺少构建器覆盖范围,并且缓存命中率低。