[iOS]CCache 让你的编译时间飞起来

6,403 阅读5分钟

原文地址:Using ccache for Fun and Profit 作者 Peter Steinberger

我们的 PSPDFKit 项目超过 60 万行代码,并且代码量还在增长。尽管我们致力于写简洁而高效的代码,但是这个项目很大,而且有许多边界情况需要尤其注意。在 PSPDFKit 5 for iOS 项目上,编译时间尤其成为一个令人头痛的问题:每次编译都很慢。

我们的安卓 SDK 也有同样的问题,几个月前我们的安卓负责人在技术栈中引入了 ccache 来处理冗长的 C++ NDK 编译时间,我也是从那个时候开始接触 ccache。

ccache 是个啥?

ccache 是一个编译缓存器,它会在实际编译之前先检查缓存。它有直接和预处理模式,而且由于在 Clang 3.2 版本之前是不支持 ccache 插件,所以在 Clang 3.2 之前会有一些问题,但是现在 Clang 的版本是 3.2.3,所以没有 Clang 不支持的问题。ccache 是一个具有悠久历史的项目,其主要焦点是快速正确。

网上搜到“ccache xcode”的信息都是过时无效的信息,经过我快速的尝试网上的方法,都无法配置好使其正常工作。随着我们的代码库越来越复杂,同时我们的 Jenkins 工作集群数也有 10 台 Mac,现在测试时间从几乎无法忍受变成了正真无法忍受。在 Twitter 抱怨现在每天的工作就是管理 Jenkins 工作集群之后,Facebook 的 Christian Legnitto(他之前在 Apple 负责 OS X 版本管理工作)建议我们尝试 ccache

Let’s get started

使用以下命令安装 ccache

 brew install ccache

如果你没安装 Homebrew,请移步这里,先去安装 Homebrew,如果你不想移步,就直接使用以下命令安装 Homebrew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

为了让 Xcode 调用 ccache,我们需要一个小脚本来配置一些环境变量,然后再调用 ccache。将这个脚本保存到您项目的某个地方,并将其命名为 ccache-clang

#!/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,file_stat_matches  
exec ccache /usr/bin/clang "$@"
else
exec clang "$@" 
fi

根据你的具体情况,如果你的项目中有 C++的文件,你可能还需要一个命名为 ccache-clang++ 的脚本,并在这个脚本里这么写:

#!/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,file_stat_matches  
exec ccache /usr/bin/clang "$@"
else
exec clang++ "$@" 
fi

这样看起来是不是有点复杂,如果没有命中缓存,那么将会按照之前的编译方式一样编译,而不是报 ccache not found(找不到缓存)的错误(ccache 内置 shell 脚本,所以检查缓存很迅速)。

创建 shell 脚本方法:

创建           touch ccache-clang
打开脚本        open -a xcode ccache-clang
粘贴脚本内容
执行脚本        chmod 755 ccache-clang

如果去学习 ccache 的配置,你会发现有很多选项可选。上面我们使用的是一种相当激进的缓存策略,同时运行良好。对于你自己的项目,你可能在没有 CCACHE_SLOPPINESS 的情况下开始,然后在一切运行良好的情况下一次性添加缓存。

这里最重要的参数是 CCACHE_CPP2,这个参数用于解决 Clang 将处理预处理器的文件输出,并可能会发现许多你没有注意到的潜在问题,例如由于宏扩展导致的不必要的括号。使用此选项会稍微减慢编译时间,但是要比完全没有使用 ccache 要快得多。Peter Eisentraut 写了一篇关于这个问题的好文章

您还需要在 Xcode 中定义 CC 变量。在 PSPDFKit 中,我们在 .xcconfig 文件中执行此操作,这个文件在我们所有项目中共享(这是一个很好的统一的项目配置,和易于阅读和查找)。同时,您可以直接在 Xcode 项目设置内配置:

CC = "$(SRCROOT)/../Resources/ccache-clang"

就这么多了!下次编译的时候会比正常慢一点,你可以在终端中使用 ccache -s 来查看 ccache 是否正常工作。刚开始时应该有很多缓存没有命中,但是当缓存开始渐渐替代之后的编译时,编译速度将会变得快起来。

坑来了

路不平的地方就有坑:ccache 有一些缺点。

不支持 Clangmodules,如果检测到 -fmodulesccache 就会失效。因此,为了兼容 ccache,你需要用老旧的 # import <UIKit/UIKit.h> 替换你项目中所有优雅的 @import UIKit,以及所有使用 ccache 带来的问题,比方说宏的问题。在 PSPDFKit 项目中我们采用了 Objective-C++ 的形式,当我们使用很多 C++ 代码时,就无法使用 modules 了,所以这一点(ccache 不支持 modules)并没有影响到我们。 modules 会自动链接用到的 framework,但是在禁用了 modules 以后,你需要手动添加用到的 framework,这个工作很无趣,但是也很快就做完。

还需要停止使用 .pch。苹果不推荐使用 .pch,而且一般认为使用 .pch 是不好的编程风格,哪里用到就在哪里导入会比 .pch 要好。对我们而言,删除那些 .pch 还是很容易的。当然,ccache 没法帮你缓存 Swift 文件。虽然 Swift 也使用 Clang,但是ccacheSwift 文件束手无策。也许 ccache 最终会支持 Swift,但我指望不上。因为 Swift 至今没有稳定,甚至我们要在 Swift 的两个版本之间做二进制兼容,我们没法用 Swift 来编写我们的 SDK,所以 ccache 不支持 Swift 的问题,对我们不是问题。

在编译期间,我们应该随时监视项目是否抛出不兼容的警告。请参阅“不支持的编译器”选项。我花了相当一部分时间去处理这些不兼容的问题。设置 CCACHE_LOGFILE 临时环境变量将有助于我们精确定位错误:ccache 将会提示那些标识是有问题的,以及缓存命中和未命中的具体情况。

steipete@steipete-rmbp ~ $ ccache -s
cache directory                     /Users/steipete/.ccache
primary config                      /Users/steipete/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/Cellar/ccache/3.2.3/etc/ccache.conf
cache hit (direct)                 42530
cache hit (preprocessed)           18147
cache miss                         28379
called for link                     1344
called for preprocessing             645
compile failed                         1
preprocessor error                     2
can't use precompiled header        2567
unsupported source language           12
unsupported compiler option        11564
no input file                          2
files in cache                    124223
cache size                           8.7 GB
max cache size                      15.0 GB

搞这个值不值?

给你说一下我们使用的情况,使用了 ccache 以后,我们的编译运行时间平均为 8 分钟,之前我们没有用 ccache 的时候是 14 分钟。使用 ccache 之前在最快的 MacBook Pro 上编译打包整个 PSPDFKit 需要 50 分钟,使用了之后,时间为 15 分钟。添加 ccache 到我们的技术栈是一个巨大的进步,真后悔我没有早点知道这个那么棒的工具!

Precompiled Header 问题

Anton Bukov 说通过禁用 GCC_PRECOMPILE_PREFIX_HEADER,开启 GCC_PREFIX_HEADER 的方式来处理这个问题。

NewPan 的文章集合

下面这个链接是我所有文章的一个集合目录。这些文章凡是涉及实现的,每篇文章中都有 Github 地址,Github 上都有源码。

NewPan 的文章集合索引

如果你有问题,除了在文章最后留言,还可以在微博 @盼盼_HKbuy 上给我留言,以及访问我的 Github