RFC-0247:在 Fuchsia 中启用 LTO | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 仅针对 Clang 工具链生成的目标二进制文件(非调试 build 中的内核除外)在 Fuchsia 中启用名为“链接时优化 (LTO)”的编译器功能。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2024-03-29 |
审核日期(年-月-日) | 2024-05-02 |
总结
我们建议仅针对 Clang 工具链生成的目标二进制文件(非调试 build 中的内核除外)在 Fuchsia 中启用名为链接时优化 (LTO) 的编译器功能。在本 RFC 中,我们不建议在内核和 Rust 中启用 LTO。
动机
LTO 可实现更好的运行时性能并缩减代码大小,但代价是增加构建时间。LTO 可与以下各项结合使用,帮助您实现更高性能、更安全的系统:
将 LTO 与 PGO 结合使用通常可以实现更好的运行时性能,因为 LTO 可以根据收集的配置文件做出有指导性的决策。此外,LTO 还支持使用一种称为 CFI 的安全防范技术。在 Fuchsia 上启用 LTO 和 CFI 有助于实现与其他操作系统供应商(例如 Android 和 ChromeOS)的功能一致性。
利益相关方
教员:
hjfreyer@google.com
Reviewers:
- 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.17 MiB 和 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 会将更多 build 开销转移到链接时间,链接成本会增加,并且并行处理和缓存带来的好处也会减少。
缺点:构建时间增加
启用 LTO 会增加 CI/CQ 发布 build 和开发者 build 的构建时间。我们通过在 CQ 中应用此 CL(分别使用和不使用 RBE/Goma),衡量了对干净 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
展示了仅构建 core.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
的单个二进制文件的增量构建时间开销。我们执行了以下步骤来衡量 LTO 对整个 build 流水线的影响。
- 运行
fx build driver_manager
以执行干净 build。 - 在源文件中删除此行。
- 执行增量构建并通过
NINJA_STATUS=["%es] " fx build driver_manager
测量经过的时间。
我们在下表中显示了 driver_manager
的增量构建时间开销:
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。不同的目标可能具有不同的构建时间和性能要求。例如,将 LTO 应用于某些目标可能有益,但对其他目标(例如测试)则不利。FatLTO 和统一 LTO 可能会有助于减少构建时间开销和复杂性。不过,它们尚未准备好全面发布,需要进行一些调试和测试工作。
先前技术和参考文献
从 Android 9 开始,Android 的内核和其他组件中都启用了 LTO 和 CFI。Chrome 已部署 LTO、PGO 和 CFI。
我们目前在 Fuchsia 中广泛使用 LTO,特别是我们在 Fuchsia 开发中使用的大多数主机工具(例如 ffx)默认都使用 LTO 构建,而 LTO 对提升这些工具的性能至关重要。
FEC 裁定
Fuchsia 工程委员会 (FEC) 已投票接受此 RFC,但附带一些附加条件。
首先,FEC 同意,出于 gulfem@google.com 和 phosek@google.com 在 RFC 文本和评论会话中提出的所有原因,LTO 代表了我们向合作伙伴(然后向最终用户)分发的二进制文件朝着正确方向迈进的一步。
其次,联邦选举委员会承认,此项变更的影响尚不确定。为发布 build 启用 LTO 对利益相关方的影响既有正面也有负面:最终用户将受益于更出色的性能;平台开发者会发现优化后的代码更难调试;开发者和产品所有者将受益于更小的二进制文件大小,尤其是在存储空间受限的设备上;平台开发者的构建时间会延长;基础架构会在编译上花费更多 CPU 周期,但在测试执行上花费更少的 CPU 周期;等等。没有人能预测全部影响,即使有人能预测,合理的利益相关方也可能会对总体影响是正面还是负面存在分歧。鉴于这种不确定性,我们认为工具链团队已尽职尽责。
变更生效后,我们鼓励任何认为自己受到过度负面影响的利益相关方向工具链团队提出疑虑,如果他们的疑虑未得到解决,则可上报给 FEC。一旦有新证据出现,我们随时可以重新评估此决定。
最后,我们想特别提及一组受到负面影响的利益相关方:内核之外的 C++ 平台开发者,他们会在正常开发工作流程中以发布模式进行构建。这项更改很可能会显著延长增量构建时间。虽然很遗憾,但我们认为启用 LTO 仍然是正确的选择。
不过,这确实反映了一个更大的问题,我们也深感担忧:随着我们在更多代码库中推出更智能的优化器,受影响的开发者数量将会增加,速度下降幅度也会加大。开发者不太可能确切知道其 build 速度变慢的原因,甚至可能都不会意识到速度变慢,但他们的工作效率仍然会受到影响。
为避免这种情况,我们要求暂停进行会增加构建时间的其他工具链相关更改,直到我们能够将这些更改应用于我们分发的工件,而不会影响团队中的大量开发者(考虑到团队中有大量开发者无法使用调试 build)。例如,我们可以将当前的两个主要编译模式(调试和发布)扩展为三个(可能类似于 Bazel 的“调试、优化和快速构建”编译模式)。耗时的优化只会在其中一种模式下运行,而另外两种模式将涵盖大多数开发者用例。作为反例,仅记录可供各个开发者选择采用的一组特定的低级别编译器标志是不够的,因为这些单独的标志组很难发现、缺少构建器覆盖率,并且缓存命中率较低。