RFC-0247:在 Fuchsia 中启用 LTO

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

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

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

摘要

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

动机

LTO 可以提高运行时性能并缩减代码大小,但会增加 build 时间。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、Performance、Release、Software Assembly、Toolchain 和 Zircon Kernel 团队就此 RFC 的一个版本进行了沟通。

设计

我们提交了一个 CL,以准备在非调试(即发布)build 中默认启用 LTO。我们只需在后续 CL 中将其开启即可。开发者可以使用 GN 实参完全停用 LTO,以缓解构建时间影响。

实现

我们在此 RFC 中实现了提案,并落实了必要的更改。

性能

LTO 具有以下大小和性能优势:

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

  • 性能优势:我们在 CQ 中运行了性能构建器,以衡量 LTO 的性能优势。在智能显示屏的 405 项测试中,LTO 改进了 23 项,其中一些增幅超过 10%。此外,LTO 在平台构建器中改进了 5,226 项测试中的 1,959 项。我们还看到平台构建者在性能方面取得了显著的提升,其中一些提升幅度超过 30%。

向后兼容性

无需担心向后兼容性。

安全注意事项

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

隐私注意事项

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

测试

我们计划在默认情况下在 ToT 上启用 LTO 后,依靠自动化测试来标记任何功能或性能问题。

文档

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

缺点、替代方案和未知因素

在编译各个模块时,编译器可进行的优化有限,而 LTO 通过在链接时执行优化,将优化范围扩大到整个程序。这样会增加链接时间,但可以通过全程序分析和跨模块优化来提高运行时性能。缺点是,LTO 会将更多 build 成本转移到链接时间,从而导致链接成本更高,并且从并行化和缓存中获得的收益更少。

缺点:构建时间增加

启用 LTO 会增加 CI/CQ 发布 build 和开发者 build 的 build 时间。我们通过应用此 CL(使用和不使用 RBE/Goma)测量了干净 build 在 CQ 中的 build 时间影响,并以 MM:SS 格式报告了以下 ninja 步骤的时间。

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

在下表中,我们展示了运行 NINJA_STATUS=["%es] " fx build 时,开发者 build 时间对仅构建核心的 x64-release 配置的完全清理 build 的影响。

RBE 无 LTO LTO 时间变化
无 RBE/Goma 26:48 27:15 +0:27
RBE 冷缓存 13:58 17:52 +3:54
RBE 暖缓存 13:55 15:53 +1:58

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

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

我们在下表中显示了 driver_manager 的增量 build 时间开销:

RBE 无 LTO LTO 时间变化
无 RBE/Goma 0:43 1:13 +0:30
RBE 1:08 1:22 +0:14

未知:发现潜在问题

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

未知:对可调试性的影响

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

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

我们计划研究在内核中启用 LTO 的好处,并在内核中启用 LTO 之前,尝试将 LTO 与 PGO 等其他优化方式相结合。我们决定将在用户空间中启用 LTO 的优势与在内核中启用 LTO 的优势分开,并使内核不在本 RFC 的范围内。

未来工作:在 Rust 中启用 LTO

我们还计划在 Rust 中启用 LTO。初步结果表明,在 Rust 中启用 LTO 可进一步提升大小和性能。不过,我们决定采用分阶段方法,在不同阶段启用 LTO,以最大限度减少构建时间影响和发布风险。

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

我们计划在 Fuchsia 中调查启用 FatLTO 和统一 LTO 的事宜。不同的目标平台可能有不同的 build 时间和性能要求。例如,对某些目标应用 LTO 可能有益,但对其他目标(例如测试)应用 LTO 可能无益。FatLTO 和 Unified LTO 可能会有助于减少构建时间开销和复杂性。不过,它们尚未准备好进行全面发布,还需要进行一些调试和测试。

现有技术和参考资料

自 Android 9 以来,Android 一直在内核和其他组件中启用 LTO 和 CFI。Chrome 已部署 LTOPGOCFI

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

FEC 决定

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

首先,FEC 同意,对于我们提供给合作伙伴(然后提供给最终用户)的二进制文件,LTO 是朝着正确方向迈出的一步,原因如下:gulfem@google.com 和 phosek@google.com 在 RFC 文本和评论串中提出的所有原因。

其次,FEC 承认此变更的影响尚不确定。 为发布 build 启用 LTO 会对相关方产生正面和负面影响:最终用户将受益于性能的提升;平台开发者会发现优化后的代码更难调试;开发者和产品负责人将受益于更小的二进制文件大小,尤其是在存储空间有限的设备上;平台开发者会发现构建时间更长;基础架构会在编译上花费更多 CPU 周期,但在测试执行上花费更少;等等。没有人能预测到全部影响,即使有人能预测到,合理的利益相关方也可能会对总体影响是正面还是负面产生异议。鉴于这种不确定性,我们认为 Toolchain 团队已尽到应有的谨慎义务。

更改生效后,如果任何利益相关者认为自己受到了过度的负面影响,我们建议他们向工具链团队提出疑虑,如果疑虑未得到解决,则可向 FEC 升级。随着新证据的出现,我们随时可以重新评估此决定。

最后,我们想特别提及一个受到负面影响的利益相关者群体:在正常开发工作流程中以发布模式构建的内核外部 C++ 平台开发者。此变更很可能会导致增量构建时间明显变慢。虽然这令人遗憾,但我们认为启用 LTO 仍然是正确的选择。

不过,这确实指出了一个我们也有的更大担忧:随着我们在更多代码库中推出更智能的优化器,受影响的开发者群体将会扩大,减速程度也会增加。开发者不太可能确切知道其 build 变慢的原因,甚至可能不会有意识地注意到变慢,但其工作效率仍会受到损害。

为避免这种情况,我们呼吁暂停进行会增加 build 时间的其他工具链相关更改,直到我们可以将这些更改应用于我们交付的制品,而不会对团队中的大量开发者造成影响(考虑到团队中有大量开发者无法使用调试 build)。例如,我们可以将当前的两种主要编译模式(调试和发布)改为三种(可能与 Bazel 的“debug、opt 和 fastbuild”编译模式类似),从而解除进一步更改的限制。高成本优化仅在其中一种模式下运行,而其他两种模式则涵盖了绝大多数开发者使用情形。举个反例,记录一组特定的低级编译器标志(供个别开发者选择采用)是不够的,因为这些个别的标志组难以发现、缺乏构建器覆盖率,并且缓存命中率很低。