[TOC]程序员
过慢的编译速度有很是明显的反作用。一方面,程序员在等待打包的过程当中可能会分心,好比刷刷朋友圈,看条新闻等等。这种认知上下文的切换会带来不少隐形的时间浪费。另外一方面,大部分 app 都有本身的持续集成工具,若是打包速度太慢, 会影响整个团队的开发进度。shell
所以,本文会分别讨论平常开发和持续集成这两种场景,分析打包速度慢的瓶颈所在,以及对应的解决方案。利用这些方案,笔者成功的把公司 app 的持续集成时间从 45 min 成功的减小到 9 min,效率提高高达 80%,理论上打包速度能够提高 10 倍以上。若是用一句话总结就是:npm
在绝对的实力(硬件)面前,一切技巧(软件)都是浮云xcode
其实平常开发的优化空间并不大,由于默认状况下 Xcode 会使用上次编译时留下的缓存,也就是所谓的增量编译。所以,平常开发的主要耗时由三部分构成:缓存
总耗时 = 增量编译 + 连接 + 生成调试信息(dSYM)性能优化
这里的增量编译耗时比较短,即便是在我 14 年高配的 MacBook Pro(4核心,8 线程,2.5GHz i7 4870HQ,下文简称 MBP) 上,也仅仅耗时十秒上下。咱们的应用代码量大约一百多万行,业内超过这个量级的应用应该很少。连接和生成调试信息各花费不到 20s,所以一次增量的编译的时间开销在半分钟到一分钟左右,咱们逐个分析:bash
平常开发的优化空间不大,即便是庞大的项目,落后的机器性能,关闭 dSYM 之后也就耗时 30s 左右。相比之下,打包速度能够优化和讨论的地方就比较多了。服务器
在利用 Jenkins 等工具进行持续集成时,缓存不推荐被使用。这是由于苹果的缓存不够稳定,在某些状况下还存在 bug。好比明明本地已经修复了 bug,能够编译经过,但上次的编译缓存没有被正确清理,致使在打包机器上依然没法编译经过。或者本地明明写出了 bug,但一样因为缓存问题,打包机器依然能够编译经过。网络
所以,不管是手动删除 Derived Data
文件夹,仍是调用 xcodebuild clean
命令,都会把缓存清空。或者直接使用 xcodebuild archive
,会自动忽略缓存。每次都要所有重编译是致使打包速度慢的根本缘由。以咱们的项目为例,总计 45min 的打包时间中,有 40min 都在执行 xcodebuild
这一行命令。架构
最天然的想法就是使用缓存了,既然苹果的缓存不靠谱,那么就找一个靠谱的缓存,好比 CCache。它是基于编译器层面的缓存,根据目前反馈的状况看,并不存在缓存不一致的问题。根据笔者的实验,使用 CCache 确实可以较大幅度的提高打包速度,删除缓存并使用 CCache 重编译后,耗时只有十几分钟。
然而,CCache 最致命的问题是不支持 PCH 文件和 Clang modules。PCH 的本意是优化编译时间,咱们假设有一个头文件 A 依赖了 M 个头文件,其中每一个被依赖的头文件又依赖了 N 个 头文件,以下图所示:
因为 #import
的本质就是把被依赖头文件的内容拷贝到本身的头文件中来,所以头文件 A 中实际上包含了 M N 个头文件的内容,也就须要 M N 次文件 IO 和相关处理。当项目中每增长一个依赖头文件 A 的文件,就会重复一次上述的 M * N 复杂度的过程。
PCH 文件的好处是,这个文件中的头文件只会被编译一次并缓存下来,而后添加到项目中 全部 的头文件中去。上述问题却是解决了,但很智障的一点是,全部文件都会隐式的依赖全部 PCH 中的文件,而真正须要被全局依赖的文件其实很是少。所以实际开发中,更多的人会把 PCH 当成一种快速 import
的手段,而非编译性能的优化。前文解释过,PCH 文件一旦发生修改,会致使不折不扣,完完整整的项目重编译,从而下降编译速度。正是由于 PCH 的反作用甚至抵消了它带来的优化,苹果已经默认不使用 PCH 文件了。
用来取代 PCH 的就是 Clang modules 技术,对于开启了这一选项的项目,咱们能够用 @import
来替代过去的 #import
,好比:
@import UIKit;复制代码
等价于
#import <UIKit/UIKit.h>复制代码
抛开自动连接 framework 这些小特性不谈,Clang modules 能够理解为模块化的 PCH,它具有了 PCH 能够缓存头文件的优势,同时提供了更细粒度的引用。
说回到 CCache,因为它不支持 PCH 和 Clang modules,致使没法在咱们的项目中应用。即便能够用,也会拖累项目的技术升级,以这种代价来换取缓存,只怕是得不偿失。
distcc 是一种分布式编译工具,能够把须要被编译的文件发送到其余机器上编译,而后接收编译产物。然而,通过贴吧、贝聊、手Q 等应用的多方实验,发现并不适合 iOS 应用。它的原理是多个客户端共同编译,可是绝大多数文件其实编译时间很是短,并不值得经过网络来回传送,这种方案应该只适合单个文件体量很是大的项目。在咱们的项目中,使用 distcc
大幅度 增长了打包时间,大约耗时 1 小时左右。
在寻求外部工具无果后,笔者开始尝试着对编译时间直接作优化。为了搞清楚这 40min 到底是如何花费的,我首先对 xcodebuild
的输出结果进行详细分析。
使用过 xcodebuild
命令的人都会知道,它的输出结果对开发者并不友好,几乎没有可读性,好在还有 xcpretty
这个工具能够格式化它:
gem install xcpretty复制代码
经过 gem
安装后,只要把 xcodebuild
的输出结果经过管道传给 xcpretty
便可:
xcodebuild -scheme Release ... | xcpretty复制代码
下面是官方文档中的 Demo:
我只对其中的编译部分感兴趣,因此简单的作下过滤,咱们就能够获得格式高度统一的输出:
Compiling A.m
Compiling B.m
Compiling ...
Compiling N.m复制代码
到了这一步,终于能够作最关键的计算了,咱们能够经过设置定时器,计算相邻两行输出之间的间隔,这个间隔就是文件的编译时间。固然,也有相似的辅助工具作好了这个逻辑:
npm install gnomon复制代码
简单的作一下排序,就能够看到最耗时的前 200 个文件了,还能够针对文件后缀做区分,计算总耗时等等。通过排查,咱们发现一半的编译时间都花在了编译 protobuf 文件上。
除了针对超长耗时的文件进行 case-by-case 的分析外,另外一种方案是调整工程设置。通常来讲,咱们的持续集成工具主要是用来给产品经理或者测试人员使用,用来体验功能或者验证 Bug,除非是须要上架 App Store,不然并不须要关心运行时性能。然而在手机上使用的 Release 模式,默认会开启各类优化,这些优化都是牺牲编译性能,换取运行时速度,对于上架的包而言无可厚非,但对于那些 Daily Build 包来讲,就显得得不偿失了。
所以,加速打包的思路和优化的思路是彻底互逆的,咱们要作的就是关闭一切可能的优化。这里推荐一篇文章:关于Xcode编译性能优化的研究工做总结,能够说至关全面了。
通过对其中各个参数的查找资料和尝试关闭,按照提高速度的降序排列,简单整理几个:
Optimize level
改为 O0,表示不作任何优化。Derived Data
目录下,所以若是内存足够,能够考虑划出 4G 左右的内存,建一个虚拟磁盘,这样将会把磁盘 IO 优化为 内存 IO,从而提升速度。因为打包机器每次都会重编译,所以并不须要担忧重启机器后缓存丢失的问题。在以上几个操做中,精简指令集的做用最大,大约能够把编译时间从 45 min 减小到 30min 之内,配合关闭编译优化,能够进一步把打包时间减小到 20min。虚拟磁盘大约能够减小两三分钟的编译时间,dSYM 耗时大约二十秒,其它选项的优化程度更低,大约在几秒左右,没有精确测算。
所以,通常来讲 只要精简指令集并关闭优化便可,有条件的机器可使用虚拟磁盘,不建议再作其它修改。
二进制化主要指的是利静态库代替源码,避免编译。前文已经介绍过如何分析文件的耗时,所以二进制化的收益很是容易计算出来。因为团队分工问题,笔者没有什么二进制化的经验,通常来讲这个优化比较适合基础架构组去实施。
以上主要是经过修改软件的方式来加速打包,自从公司申请了 2013 年款 Mac Pro(Xeon-E5 1630 6 核 12 线程,16G 内存,256G SSD 标配,下文简称 Mac Pro)后,不须要修改任何配置,仅仅是简单的迁移打包机器,就能够把打包时间下降到 15 min,配和上一节中的前三条优化,最终的打包时间大概在 10min 之内。
在个人黑苹果(i7 7820x 8 核 16 线程,16G 内存,三星 PM 961 512G SSD,下文简称黑苹果)上,即便不开启任何优化,从零开始编译也仅需 5min。若是将 protobuf 文件二进制化,再配合一些工程设置的优化,我不敢想象须要花多长时间,预计在 4min 左右吧,速度提高了大概 11 倍。
编译是一个考验多核性能的操做,在个人黑苹果上,编译时能够看到 8 个 CPU 的负载都达到了 100%,所以在必定范围内(好比 10 核之内),提高 CPU 核数远比提高单核主频对编译速度的影响大。至于某些 20 核以上、单核性能较低的 CPU 编译性能如何,但愿有经验的读者给予反馈。
下表总结了文章中提到的各类优化手段带来的速度提高,参考原始时间均为 45 min(打包机器:13 寸 MacBook Pro):
方案序号 | 优化方案 | 优化后耗时 (min) | 时间减小百分比 |
---|---|---|---|
1 | 不常修改的文件二进制化 | 25 | 44.4% |
2 | 精简指令集 | 27 | 40% |
3 | 关闭编译优化 | 38 | 15.6% |
4 | 使用 Mac Pro | 15 | 66.7% |
5 | 虚拟磁盘 | 42 | 6.7% |
6 | 公司现行方案(2+3+4+5) | 9 | 80% |
7 | 黑苹果 | 5 | 88.9% |
8 | 终极方案(1+2+3+5+7) | 4(预计) | 91.1%(预计) |
严格意义上讲,文章有点标题党了,由于一句话来讲就是:
能用硬件解决的问题,就不要用软件解决。