之前介绍了一下 iOS中的编译过程 iOS编译过程 。 现在来对整体编译时间做一个优化。
编译耗时分析
xcode完整的build详细的步骤如下:
- 创建Product.app的文件夹
- 把Entitlements.plist写入到DerivedData里,处理打包的时候需要的信息(比如application-identifier)。
- 创建一些辅助文件,比如各种.hmap,这是headermap文件,具体作用下文会讲解。
- 执行CocoaPods的编译前脚本:检查Manifest.lock文件。
- 编译.m文件,生成.o文件。
- 链接动态库,o文件,生成一个mach o格式的可执行文件。
- 编译assets,编译storyboard,链接storyboard
- 拷贝动态库Logger.framework,并且对其签名
- 执行CocoaPods编译后脚本:拷贝CocoaPods Target生成的Framework
- 对Demo.App签名,并验证(validate)
- 生成Product.app
获取项目整体编译时长:Xcode Build Timing Summary
Xcode Build Timing Summary 是Xcode10中加入的用于查看获取构建时间和发现用时瓶颈方面的最有利工具。 可以通过Product->Perform Action->Build With Timing Summary来开启:这样在 Build Log 的末尾就会添加 Timing Summary Log。我们可以通过这个 log 看到哪个阶段是耗时的,便于我们进行优化
从这个选项看出
YYKITDemo项目耗时最多的是 compileC
compileC就对应上面讲述单个文件的编译过程,那么怎么看单个文件和整个工程文件的编译时长呢?
获取单个文件编译时长:-ftime-trace 参数
clang9.0合并了 -ftime-trace 参数功能,可以获得单个文件的编译时长。
clang/llvm 每个文件一个编译单元,逐个文件进行编译,最后链接生成可执行文件,-ftime-trace 通过在各个编译过程插桩,输出每个编译阶段的时长。
测试文件: main.c
1 | xcrun clang -ftime-trace -fmodules -c main.c -o main.o |
可以在 chrome://tracing/
中打开查看生成的统计文件main.json
xcode目前带的clang已经版本已经超过9.0可以使用这个功能:1
2
3
4
5xcrun clang --version
Apple clang version 13.0.0 (clang-1300.0.29.3)
Target: x86_64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
在project中配置:
编译后形成的统计json:
因为它生成都是单个文件的 写个脚本合并一下json文件:
由整体耗时可以看出:
1)编译器前端处理(Frontend)耗时 34.95s,占整体ExecuteCompiler43.538s 80.27%;
2)而前端处理下头文件处理(Source)则占据Frontend绝大部分时间
猜测:头文件嵌套严重,每个源文件都要引入几十个甚至几百个头文件,每个头文件源码要做预处理、词法分析、语法分析等等。实际上源文件不需要使用某些头文件里的定义(如 class、function),所以编译时间才那么长。
于是再写一个脚本用于统计分析上面分散的json信息:
可以尝试优化 TopN 头文件里的头文件引用,尽量不包含其他头文件。
[xcode配置项] Build succeeded
将 Debug Information Format 改为 DWARF:
将 Build Active Architecture Only 改为 Yes:
优化头文件搜索路径:search header
关闭 Enable Index-While-Building Functionality:
enable module
修改编译线程数[头文件引用]
使用 PCH 预编译头文件
写一个算法,分析出头文件被引用的次数
将头文件被引入最多的头文件放入pch中
尝试优化 TopN 头文件里的头文件引用,尽量不包含其他头文件。
去掉不必要的文件引入
头文件中尽量使用@class来标识类
POPO项目编译时间优化实践
- POPO项目
- 电脑配置:
- 编译时长 1184s:
- 头文件优化
- 分析一下 ftime-trace.json文件:
- 部分文件改动:
- 优化结果
- 编译时长 908s:
- 分析ftime-trace.json文件:
优化结果 : 降低 276s (23.3%)
未来优化方向
二进制包
无用头文件 IWYU
资源IO处理事件减少
[hmap插件]
原理: 编译时候要找到对应的文件,但是这个查找过程十分耗时。虽然Xcode自带了hmap的功能,但是相关条件下,并不能提升编译速度。使用hmap相关插件解决问题后,就可以省去pod库相关文件查找时间,减少编译时长。
题外话:clang插件的扩展性
这里就提一下可以自定义clang插件实现自己想要的功能,比如clang 分析ast数查找无用代码
著名的Appcode有一个功能就是帮你找无用头文件,其原理猜测也是通过clang自定义插件 分析ast实现的
IWYU 使用 clang 分析符号的引用。 是 google 的一个项目,它可以给出应该引用和移除的头文件,但并不能保证 100% 是正确的(暂未适配OC,适用c c++)
我们也可以通过下载clang代码编写插件来实现比如变量名格式校验:
插件效果:
插件代码: