快速链接:改善构建和启动时间
Link fast: Improve build and launch times
2022年6月6日
一句话判断
如果你的应用构建慢或启动慢,问题很可能出在链接环节——这场 Session 把静态链接和动态链接的原理和优化手段讲得极其透彻。
这场 Session 讲了什么
Apple 链接器团队的首席工程师 Nick Kledzik 全面讲解了链接过程的工作原理和性能优化。内容覆盖静态链接(影响构建时间)和动态链接(影响启动时间)两个方面。
Session 从链接的历史演变开始讲起,解释了 .o 文件、静态库、动态库的产生动机和工作机制。然后介绍了 Apple 静态链接器 ld64 今年的性能提升——对许多项目来说速度翻倍。接着深入讲解了动态链接器 dyld 的新改进。
最后介绍了两个新工具:dyld_usage 和 nm,帮助开发者洞察二进制文件内容和动态链接过程。
值得深挖的点
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,它默认生成静态库,但某些配置可能影响链接效率