11、编译缓存与增量编译:ccache 配置、ninja 增量编译原理、清理编译产物
说到 Android 编译,我猜你肯定遇到过这种情况:改了一行 Java 代码,结果重新编译等了半小时。嗯,这其实不是你的错,而是构建系统在「重新发明轮子」。今天我们就来聊聊怎么让编译变快——说白了,就是让机器少干点重复劳动。
为什么需要编译缓存?
先想一个问题:你编译一次完整的 Android 系统,可能要生成几万个 .o 文件。如果你只改了一个 .c 文件,理论上只需要重新编译这一个文件,然后重新链接就行。但现实是,很多构建系统会「过度编译」——它不确定哪些文件受影响,干脆全部重来。
我刚开始做 Android 系统开发时,就吃过这个亏。有一次我改了一个头文件,结果整个 framework 层重新编译了 40 分钟。后来才发现,是 ccache 没配好,缓存根本没命中。
核心思想:编译缓存的核心就是「以空间换时间」。把编译结果存起来,下次遇到同样的输入(源文件 + 编译选项),直接返回缓存结果,跳过真正的编译过程。
ccache 配置实战
ccache 是 C/C++ 编译器的缓存工具。在 Android 构建中,它主要缓存 .o 文件的编译结果。配置其实很简单,但有几个坑要注意。
1. 安装 ccache
# Ubuntu/Debian
sudo apt-get install ccache
# macOS
brew install ccache
2. 配置环境变量
我个人习惯在 ~/.bashrc 或 ~/.zshrc 里加上这几行:
export USE_CCACHE=1
export CCACHE_DIR=/home/yourname/.ccache
export CCACHE_MAXSIZE=50G
export CCACHE_COMPRESS=1
export CCACHE_COMPRESSLEVEL=6
这里解释一下每个参数:
| 参数 | 说明 | 我推荐的值 |
|---|---|---|
| CCACHE_DIR | 缓存存放路径 | 建议放在 SSD 上 |
| CCACHE_MAXSIZE | 缓存最大容量 | 50G ~ 100G |
| CCACHE_COMPRESS | 是否压缩缓存 | 1(开启) |
| CCACHE_COMPRESSLEVEL | 压缩级别 | 6(平衡速度和大小) |
小技巧:缓存目录一定要放在 SSD 上。我曾经放在 HDD 上,结果缓存读写比编译还慢,得不偿失。
3. 初始化缓存
# 设置缓存大小
ccache -M 50G
# 查看缓存状态
ccache -s
运行 ccache -s 后,你会看到类似这样的输出:
cache directory /home/yourname/.ccache
primary config /home/yourname/.ccache/ccache.conf
secondary config (readonly) /etc/ccache.conf
cache hit (direct) 12345
cache hit (preprocessed) 6789
cache miss 2345
cache hit rate 89.2 %
...
看到 cache hit rate 在 80% 以上,说明配置生效了。
ninja 增量编译原理
ccache 解决的是「单个文件编译」的缓存问题。而 ninja 解决的是「哪些文件需要重新编译」的问题。两者配合,才能达到最快的增量编译速度。
ninja 的增量编译原理,说白了就是「依赖图 + 时间戳」。它维护一个 .ninja_log 文件,记录每个目标的编译时间和依赖关系。当你再次编译时,ninja 会做三件事:
- 检查依赖变化:比较源文件和目标文件的时间戳
- 构建依赖图:找出所有受影响的节点
- 只编译变化的部分:跳过未变化的节点
举个例子,假设你有这样的依赖关系:
app.o ← app.c ← app.h
← utils.h
如果你改了 app.c,ninja 只会重新编译 app.o。但如果你改了 app.h,所有包含这个头文件的 .c 文件都会被重新编译。
注意:ninja 的增量编译依赖时间戳。如果你用 git checkout 切换分支,文件时间戳可能会被重置,导致 ninja 认为所有文件都变了。这时候建议先 touch 一下关键文件,或者直接 make clean 重新编译。
清理编译产物
编译产物多了,磁盘空间会告急。而且有时候缓存「脏」了,需要彻底清理。我总结了几种清理方式:
1. 轻度清理:只清理临时文件
# 清理 .o 文件,保留 .so 和 .apk
make clean
# 或者用 ninja
ninja -t clean
2. 中度清理:清理所有编译产物
# 回到初始状态
make clobber
# 或者直接删掉 out 目录
rm -rf out/
3. 重度清理:连 ccache 一起清
# 清空 ccache
ccache -C
# 查看清理后的缓存状态
ccache -s
我的经验:不要频繁 make clobber。我见过有人每次编译前都 clean 一次,结果每次都是全量编译,白白浪费半小时。只有在遇到诡异的编译错误时,才考虑彻底清理。
知识体系总览
下面这张图总结了本章的核心逻辑:
避坑指南
最后分享几个我踩过的坑:
- ccache 缓存太大导致磁盘满:我曾经设了 100G 缓存,结果忘了限制,最后磁盘爆了。建议设 50G 上限,定期用
ccache -s检查。 - ninja 增量编译失效:如果你发现改了代码但 ninja 不重新编译,可能是时间戳没更新。试试
touch一下源文件。 - 清理后编译变慢:清理 ccache 后,第一次编译会变慢。这是正常的,因为缓存需要重新建立。
嗯,关于编译缓存和增量编译,今天就聊这么多。记住一个原则:能不编译就不编译,能复用就复用。这样你的开发效率至少能提升一倍。
公众号:蓝海资料掘金营,微信deep3321