Link fast: Improve build and launch times
System & Services 进阶 20m

快速链接:改善构建和启动时间

Link fast: Improve build and launch times

2022年6月6日

在 Apple 官方观看视频

一句话判断

如果你的应用构建慢或启动慢,问题很可能出在链接环节——这场 Session 把静态链接和动态链接的原理和优化手段讲得极其透彻。

这场 Session 讲了什么

Apple 链接器团队的首席工程师 Nick Kledzik 全面讲解了链接过程的工作原理和性能优化。内容覆盖静态链接(影响构建时间)和动态链接(影响启动时间)两个方面。

Session 从链接的历史演变开始讲起,解释了 .o 文件、静态库、动态库的产生动机和工作机制。然后介绍了 Apple 静态链接器 ld64 今年的性能提升——对许多项目来说速度翻倍。接着深入讲解了动态链接器 dyld 的新改进。

最后介绍了两个新工具:dyld_usagenm,帮助开发者洞察二进制文件内容和动态链接过程。

值得深挖的点

ld64 速度翻倍的实现方式。 Apple 从三个维度优化了静态链接器:利用多核并行处理(并行拷贝内容、并行构建 LINKEDIT 各部分、并行计算 UUID 和代码签名哈希)、改进算法(exports-trie 构建器改用 C++ string_view)、以及使用硬件加速的加密库。这些优化不需要开发者做任何配置变更就能受益。

静态库的选择性加载机制。 链接器处理静态库时不是全部加载,而是只在有未定义符号需要解析时才从中提取对应的 .o 文件。这意味着即使静态库很大,最终产物只包含实际用到的部分。但这也有一个副作用:如果库之间的依赖顺序不对,某些 .o 文件可能不会被加载,导致链接错误。

动态链接的性能瓶颈。 应用启动时,dyld 需要加载所有动态库、修复指针、运行初始化器。每个动态库都有启动开销。Apple 建议:能用静态库就不用动态库;必须用动态库的话,确保合并小动态库、延迟加载非必要库。

新的诊断工具。 dyld_usage 可以追踪应用启动时的动态链接活动,帮你发现哪些动态库加载最慢、哪些符号解析最耗时。nm 工具则可以查看二进制文件中的符号表,了解哪些函数和数据占用了空间。

代码片段

# 使用 dyld_usage 追踪动态链接活动
sudo dyld_usage -launch <app_bundle_id>
# 可以看到每个动态库的加载时间和符号修复过程

# 使用 nm 查看二进制符号表
nm -gU <path_to_binary>
# 列出所有导出的全局符号

# 查看动态库依赖关系
otool -L <path_to_binary>
# 列出所有链接的动态库

# 静态库的构建建议:
# 1. 正在积极开发的代码不要放进静态库
#    因为每次编译后都要重建整个静态库
# 2. 稳定的代码适合放在静态库中
# 3. 静态库在命令行中的顺序很重要
#    被依赖的库应该放在后面

最佳实践

  • 正在积极开发的源文件不要放进静态库——每次编译后重建静态库是额外的 I/O 开销
  • 减少动态库数量:多个小动态库应该合并成一个,因为每个动态库都有独立的启动开销
  • 关注链接器的 -all_load-force_load 选项,它们会绕过选择性加载机制,让静态库的所有 .o 文件都被加载
  • 使用 dyld_usage 工具分析应用启动时的链接瓶颈
  • 考虑使用静态链接替代动态链接来减少启动时间
  • 注意静态库的命令行顺序——依赖其他库的库应该排在前面

还有什么值得关注

  • Session 提到 Apple 正在持续改进 dyld 的性能,未来版本可能有更多优化
  • 对于大型项目,链接时间可能占到总构建时间的很大比例,值得专门优化
  • 动态库的初始化器(initializer)也会影响启动时间,应该尽量精简
  • 如果你使用 CocoaPods,它默认生成静态库,但某些配置可能影响链接效率
WWDC 2022