iOS 客户端编译优化实践

前言

公司虽然有专门的打包机,但是打包机打一次包总耗时30min左右。 在平常可能还没有什么关系,但是到了回归阶段就很难受了。

测试回归阶段发现一个bug,等开发定位问题->修复->打包->测试验证,至少是一个小时的周期。

更可怕的是才打了一个包,测试又发现一个bug……

目前编译时间已经影响到了开发效率。2021年上半年,和同事一起做了编译优化

注:这篇文章并没有去解决增量编译问题,因为增量编译目前Xcode做的已经足够好,在开发过程中瓶颈并不大;而打包机每次都是全量编译,我们这里主要是解决全量编译耗时。

了解编译原理和过程

编译原理

苹果使用的是LLVM来编译项目的,其中Clang作为前端处理。

image.png

本篇偏重于实践操作,具体原理可以看这里: 传送门

Xcode编译日志查看项目的编译顺序。

image.png 从这里可以看到相对来说整体全面的编译信息。也可以看的某个文件的编译耗时。

这里我有一个未解的问题:每个文件后面的耗时是什么意思?为什么各个文件的耗时总和与总时间对不上?求大神解答

个人猜测是这里是利用多核CPU并发执行的结果。

我们根据Xcode的编译日志,总结了我们项目的编译大致过程:

image.png

对于某个具体文件的编译过程可以通过Clang来查看:

使用clang -ccc-print-phases -framework Foundation AFNetworking-dummy.m -o AFNetworking-dummy.m命令,终端执行得到下面信息: image.png

使用ClangTime_Trace功能来查看

苹果编译使用的是LLVM。因为开源的LLVM已经有大神加入了各个节点的耗时埋点。所以我们做的第一步是使用开源的LLVM来编译一次项目,拿到分析报告。

具体操作如下:

1.下载开源的LLVM

注:当前的Xcode自带Clang应该已经支持,所以其实不需要下载,可以利用终端 clang --version查看版本以及路径。 但是自己在实践过程中并未成功,因此还是使用了下载的。

2.设置XcodeBuild settingsUser-Defined

image-20210402091036622.png

3.设置生成编译耗时的参数

image-20210402091129039.png

4.为了防止报错,修改这个参数

image-20210402091202710.png

5.进行项目编译
6.编译后查找对应的json分析文件

文件路径在图下面的红色框里 image-20210402091329007.png

7.使用chrome://tracing 或者 https://www.speedscope.app 查看对应的Json文件进行分析

image-20210402091450504.png

image-20210402091739946.png

拿到项目里的耗时图

下图的分析数据是经过自己写的python脚本聚合,其他人可以参考这篇博客的脚本 image-20210402155430704.png

前端耗时是3876s 占比:96%
source的耗时是3562s 占整体比为:88%
module load是 1710s

说明我们的耗时还是集中在了source环节上,因此解决掉source这一环节,就能很大提升项目的编译速度。通过这篇微信的优化文章了解到,这个环节主要是耗时在头文件处理上。

关于module Load

这个是Xcode增加的一个特性。控制开关在Xcode的Build settings里面:

image.png

苹果文档介绍:Enables the use of modules for system APIs. System headers are imported as semantic modules instead of raw headers. This can result in faster builds and project indexing.

专门对一个工程进行了开启和关闭验证。

打开这个开关有两个好处:

 1.系统的类库不需要再显示的在Xcode的 build settings 中引入对应类库

 2.通过编译耗时对比,明显对于类的编译减少了很多(对于一个自定义的View,开启的情况下编译耗时150ms左右,关闭的情况下解决400ms)。
复制代码

编译时间消耗点

  • 每个引入的文件都会进行编译;

  • 每个引入的资源文件都需要copy进入包里;

  • 每个xib或者storyboard文件也需要编译并copy进包里

  • 全量编译的情况下,每个pod库每次都要重新编译

  • 在编译中有多个脚本执行

  • 编译过程中,需要拷贝swift的标准库(这个耗时从Xcode中看,一个指令集下都要耗时30s多)

……

减少编译时间的思路:能不编译就不编译

1.减少无用文件和无用资源

  • 删除无用的库
  • 删除无用的文件

可以从可执行文件里,找到所有类的集合引用的类的集合,做一个差值拿到未使用的类的集合,可以参考:iOS 脚本查看项目中未使用的类

这种方式不适用于我们的项目。因为我们项目使用的是路由跳转,跳转指令都由服务端下发。

  • 删除无用图片

    这个网上有很多脚本,比如:LSUnusedResources

2.优化头文件引用

  • 增加pch文件

    pch文件本来被苹果抛弃,原因是容易引起滥用,滥用pch的结果是导致编译耗时增加。

    但是如果使用得当,能够极大提升开发效率并且减少编译时长。

    • 写一个算法,分析出头文件被引用的次数

    • 将头文件被引入最多的头文件放入pch中

    这里有个疑问:pch能在预处理阶段进行完,将pch里的头文件内容全部替换到所有头文件中。按照这样的情况,难道不是应该拖慢编译速度吗?

    但是事实上,增加pch文件后,我们的编译时长缩短了2min左右。

  • 项目分模块给对应的开发,负责将头文件引入优化

    这一步优化实际落地很耗时,但是因为对增量编译也有好处,需要长期进行。

    • 去掉不必要的文件引入

    • 头文件中尽量使用@class来标识类

    为什么这一步可以节省时间?头文件放在.h和.m中有什么区别?

    其实从编译角度考虑,区别并不大。因为都会在预处理阶段,将头文件内容导入进去。 但是放在.h中后,增大了该文件被其他不需要引入的类引用的风险。项目中引用很乱,因此处理这一步也能优化不少。

3.使用缓存,减少不必要的编译。

使用CC-cache

原理: 将Xcode编译文件缓存下来,在编译的时候减少不必要的重复编译。

image.png

这一步对于全量编译来说,优化最大。

我们项目里使用后,直接减少了10min的编译时间。

但是需要注意的是,它不支持上面提到的Module Enable

相关资料可以参考:传送门

4.hmap插件

原理: 编译时候要找到对应的文件,但是这个查找过程十分耗时。虽然Xcode自带了hmap的功能,但是相关条件下,并不能提升编译速度。使用hmap相关插件解决问题后,就可以省去pod库相关文件查找时间,减少编译时长。

相关原理请看这里:一款可以让大型iOS工程编译速度提升50%的工具

我们项目使用的插件是:传送门

这一步使用后,我们在原先的基础上又优化了2~3min

总结

站在巨人的肩膀上,我们通过努力将全量编译时间从22min左右,减少到了9min左右。后面还有很多其他优化空间,需要继续努力(比如:优化头文件引用,模块化,Xcode相关编译配置等等)。

猜你喜欢

转载自juejin.im/post/7004471000104435749
今日推荐