iOS编译时间优化

之前介绍了一下 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
5
xcrun 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 头文件里的头文件引用,尽量不包含其他头文件。

  1. [xcode配置项] Build succeeded
    将 Debug Information Format 改为 DWARF:
    将 Build Active Architecture Only 改为 Yes:
    优化头文件搜索路径:search header
    关闭 Enable Index-While-Building Functionality:
    enable module
    修改编译线程数

  2. [头文件引用]
    使用 PCH 预编译头文件
    写一个算法,分析出头文件被引用的次数
    将头文件被引入最多的头文件放入pch中
    尝试优化 TopN 头文件里的头文件引用,尽量不包含其他头文件。
    去掉不必要的文件引入
    头文件中尽量使用@class来标识类

POPO项目编译时间优化实践

  1. POPO项目
  • 电脑配置:
  • 编译时长 1184s:
  1. 头文件优化
  • 分析一下 ftime-trace.json文件:
  • 部分文件改动:
  1. 优化结果
  • 编译时长 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代码编写插件来实现比如变量名格式校验:

插件效果:

插件代码:

上面这个例子的链接