1、引言
岁月真是个养猪场,这几年,人胖了,微信代码也翻了。
记得 14 年转岗来微信时,用自己笔记本编译微信工程才十来分钟。如今用公司配的 17 年款 27-inch iMac 编译要接近半小时;偶然间更新完代码,又莫名其妙需要全新编译。在这么低的编译效率下,开发心情受到严重影响。
于是年初我向上头请示,优化微信编译效率,上头也同意了。
2、现有方案
在动手之前,先搜索目前已有方案,大概情况如下。
2.1 优化工程配置
1)将 Debug Information Format 改为 DWARF:
Debug 时是不需要生成符号表,可以检查一下子工程(尤其开源库)有没有设置正确。
2)将 Build Active Architecture Only 改为 Yes:
Debug 时是不需要生成全架构,可以检查一下子工程(尤其开源库)有没有设置正确。
3)优化头文件搜索路径:
避免工程 Header Search Paths 设置了路径递归引用:
Xcode 编译源文件时,会根据 Header Search Paths 自动添加 -I 参数,如果递归引用的路径下子目录越多,-I 参数也越多,编译器预处理头文件效率就越低,所以不能简单的设置路径递归引用。同样 Framework Search Paths 也类似处理。
2.2 使用 CocoaPods 管理第三方库
这是业界常用的做法,利用 cocoapods 插件 cocoapods-packager 将任意的 pod 打包成 Static Library,省去重复编译的时间;但缺点是不方便调试源码,如果库代码反复修改,需要重新生成二进制并上传到内部服务器,等等。
2.3 CCache
CCache 是一个能够把编译的中间产物缓存起来的工具,不需要过多修改项目配置,也不需要修改开发工具链。Xcode 9 有个很偶然的 bug,在源码没有任何修改的情况下经常触发全新编译,用 CCache 很好的解决这一问题。但随着 Xcode 10 修复全量编译问题,这一方案逐步弃用了。
2.4 distcc
distcc 是一个分布式编译工具,它原理是把本地多个编译任务分发到网络中多个机器,其他机器编译完成后,再把产物返回给本机上执行链接,最终得到编译结果。
2.5 硬件解决
如把 Derived Data 目录放到由内存创建的虚拟磁盘,或者购买最新款的 iMac Pro...
3、实践过程
3.1 优化编译选项
1)优化头文件搜索路径:
把一些递归引用路径去了后,整体编译速度快了 20s。
2)关闭 Enable Index-While-Building Functionality:
这选项无意中找到的(Xcode 9 的新特性?),默认打开,作用是 Xcode 编译时会顺带建立代码索引,但影响编译速度。关闭后整体编译速度快 80s(Xcode 会换回以前的方式,在空闲时间建立代码索引)。
3.2 优化 kinda
kinda 是今年引入支付跨平台框架(C++),但编译速度奇慢,一个源文件编译都要 30s。另外生成的二进制大小在 App 占比较高,感觉有不少冗余代码,理论上减少冗余代码也能加快编译速度。
经过分析 LinkMap 文件和使用 Xcode Preprocess 某些源文件,发现有以下问题:
1)proto 文件生成的代码较多;
2)某个基类/宏使用了大量模版。
对于问题一:可以设置 proto 文件选项为 optimize_for=CODE_SIZE 来让 protobuf 编译器生成精简版代码。但我是用自己的工具生成(具体原理可看《iOS版微信安装包“减肥”实战记录》),代码更少。
对于问题二:由于模版是编译期间的多态(增加代码膨胀和编译时间),所以可以把模版基类改成虚基类这种运行时的多态;另外推荐使用 hyper_function 取代 std::function,使得基类用通用函数指针,就能存储任意 lambda 回调函数,从而避免基类模板化。
例如:
template<typenameRequest, typenameResponse>
classBaseCgi {
public:
BaseCgi(Request request, std::function<void(Response &)> &callback) {
_request = request;
_callback = callback;
}
voidonRequest(std::vector<uint8_t> &outData) {
_request.toData(outData);
}
voidonResponse(std::vector<uint8_t> &inData) {
Response response;
response.fromData(inData);
callback(response);
}
public:
Request _request;
std::function<void(Response &)> _callback;
};
classCgiA : publicBaseCgi<RequestA, ResponseA> {
public:
CgiA(RequestA &request, std::function<void(Resp