优化 iOS 项目的构建时间(二)

做者介绍:钟子豪,贝聊科技高级 iOS 工程师git

前言

以前一篇介绍 CCache 的文章探讨了如何使用 CCache 来优化应用构建的时间,评论里面收到了很多朋友反馈在使用的过程遇到了困难,最后没法成功应用上 CCache。其中的绝大部分问题咱们在贝聊项目的集成过程当中也遇到过,本文主要针对这些问题给出相应的解决方案,并从其余方面给出一些优化应用构建时间的建议。swift

提高缓存命中率

经过命令 ccache -s 能够查看 CCache 的总体缓存命中率。要使 CCache 真正能减小编译时间,命中率大约要达到 90% 。前文已经提过,频繁地缓存 miss 会比不使用 CCache 还慢,所以在集成 CCache 后须要保证90%的缓存命中率,才能确实地提升构建速度,若是发现缓存命中率太低,则须要分析日志排查缘由。缓存

移除 Precompiled Prefix Header

使用了 PCH 文件是最多见的致使缓存命中率太低的缘由。不少项目都会为了方便,把一些经常使用的类在 PCH 中 import 一次,而在编译时 PCH 的内容会附加在每一个文件以前,PCH 内容的改变会致使整个编译对象的所有文件的内容改变,也就致使所有 CCache 缓存失效。若是你遇到过明明只修改了一两个源文件,运行项目却会全量编译的状况,看看是否是PCH里面import了太多依赖,若是暂时没法移除 PCH 则尽可能减小里面 import 的文件。ruby

以文件内容做为编译缓存的 Key

由于常见的 git 切换分支,pod update等操做都会形成大量文件的最后编辑时间变化,若是用文件最后编辑时间做为缓存的 key,常常会看见切换 git 分支或者 pod update 以后大量文件缓存失效。 CCache 默认就是用文件内容的 MD4 摘要值来做为缓存 key 的一部分的,只要不加上 file_stat_matches 选项便可。虽然计算并对比文件内容的摘要值比简单对比文件的修改时间和大小要耗费更长的时间,但通过实践发现使用文件内容来做为 key 会有更稳定的编译优化效果,如下是我目前在用的效果比较好的 CCache 配置:bash

#!/bin/sh
if type -p ccache >/dev/null 2>&1; then
  export CCACHE_MAXSIZE=10G
  export CCACHE_CPP2=true
  export CCACHE_HARDLINK=true
  export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,pch_defines
  
  exec ccache /usr/bin/clang "$@"
else
  exec clang "$@"
fi
复制代码

只在 Release 构建启用 CCache

在使用 Debug 配置开发期间,Xcode 自己自带增量编译,只是在使用 Release 配置构建 AdHoc 或者 AppStore 的时候不能使用增量编译。所以在 Debug 模式下启用 CCache 其实意义不大,开发时文件频繁变动致使缓存命中率低,反而会拖慢平常开发节奏。并发

对单个 Pod 不启用 CCache

使用 CCache 须要关闭Clang Module功能,而有些第三方库由于使用了@import语法致使若是关闭 Clang Module 则没法使用@import 语法继而致使编译出错。这种状况能够单独设置一个 Pod 不要启用 CCache 便可。app

下面的 Podfile 配置代码同时演示了如何只在 Release 构建启用 CCache 以及对单个 Pod 不使用 CCache:post

# Podfile

target 'YourApp' do # 替换为你的 target 名
  post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
      if config.name != 'Debug' && target.name != 'SomePod' # 替换为你想要排除的 Pod 的名字
        config.build_settings['CC'] = '$(PODS_ROOT)/ccache-clang' # 替换为你的 ccache-clang 文件路径
      end
    end
  end
end
  
复制代码

其余的优化点

这些优化点与 CCache 无关,但我尝试过以后发现对编译速度和开发体验有必定提高,所以一并列出。性能

调整编译的最大并发数

更新:Xcode 9.3 发布后,我又用一样的项目验证了一次,得出了近似的结果,但差别较小,建议先进行 Benchmark 再决定是否要应用到本身项目中。测试

我以前经过这条命令

defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks `sysctl -n hw.ncpu`
复制代码

把编译时的最大并发数设置为 CPU 的核心数,没想到这居然不是最好的配置。我最先是从下面这篇文章发现的: The best hardware to build with Swift is not what you might think | 领英

虽然原文做者说在 Xcode 9 已经修复了这个问题,但我仍是用一个纯 Objective-C 的项目作实验验证,结果以下:

编译并发数 编译三次平均耗时(秒)
4 118.3
6 110.6
8 126.5

实验结果也是让我很意外,没想到 8 线程的编译速度竟然还不如 4 线程,虽然差距不算大,但经过调整编译并发数对编译速度的确有影响,你们能够在本身的构建机器上用本身的项目实验一下,找到最优的配置。

另外我顺便用同一个项目测试了一下 Xcode 9 的 New Build System 的性能,结果以下

编译并发数 编译三次平均耗时(秒)
4 132.3
6 133.7
8 134.5

发现 New Build System 的编译速度略逊于旧的编译系统,但编译过程更加严谨,例如在 Copy Bundle Resources 步骤时,若是发现缺乏了某个图片资源文件会做为错误而不是警告抛出。若是你的项目已经切换到了 New Build System,我建议继续使用,毕竟将来苹果会针对其作大量优化,这一点性能问题应该会获得解决。

避免头文件的递归查找

在排查编译时间过长的元凶时,发如今项目的 Build Settings-> Header Search Paths 中竟然有一行 ${PROJECT_DIR}/**,多是之前某个同事遇到找不到头文件编译错误,没有细想就加进去的。这项配置会致使编译文件时在整个项目目录中递归查找头文件,显著地增长查找头文件的时间,单个文件的编译时间可能会增长2~3倍之多。

清理废弃代码

没有代码的编译速度能快得过「没有代码」,这是很显而易见的优化手段,不过实现起来可能工做量较大。在公司业务快速迭代和更新时很容易就会产生一些再也不使用的模块,按期清理既能减小项目的构建时间也能减少安装包的体积,一箭双雕。若是以为这些代码之后还会用得上,能够只移除 Xcode 项目的引用而保留下来文件以做参考。

在 Release 配置下关闭警告信息

clang 在编译的过程当中若是遇到不规范的代码,例若有未使用的变量,会生成一段警告信息并输出到控制台或者日志,处理这些警告信息也是须要占用一些的计算资源的。警告信息建议在开发阶段就处理掉,若是警告信息是来自 CocoaPods 集成的第三方库的话,能够在 Podfile 中把inhibit_all_warnings! 选项打开。贝聊的项目经过这个操做把编译时间减小了约 40 秒。

总结

咱们的项目经过上面这些手段,最终把平均的编译时间从 1300 多秒减小到 300 秒内,视乎 CCache 的命中率会有所浮动,但整体上是大幅减小,但愿这些经验能对你的项目有所帮助。

相关文章
相关标签/搜索