概述
ccache(compiler cache)是一个编译缓存工具,通过缓存编译结果来加速重复编译。OpenHarmony 构建系统通过 --ccache 参数原生支持 ccache。
1. 工作原理
源码编译请求
│
▼
ccache 计算 hash(源码内容 + 编译参数 + 编译器版本 + 头文件)
│
├─ 命中(Hit)→ 直接复制缓存的 .o 文件(毫秒级)
│
└─ 未命中(Miss)→ 调用真实 clang 编译
→ 把编译结果存入缓存
→ 返回 .o 文件
hash 计算包含的因素
| 因素 | 说明 |
|---|---|
| 源文件内容 | 文件本身的 hash |
| 预处理后内容 | 展开所有 #include 和宏后的结果 |
| 编译参数 | -O2、-DDEBUG 等所有 flags |
| 编译器版本 | clang 15.0.4 的二进制 hash |
| 目标架构 | --target=arm-linux-ohos 等 |
任何一项变化都会导致 cache miss。
2. 使用方法
基本用法
bash
cd v6.1-release
# 带 ccache 编译
./build.sh --product-name rk3568 --ccache
# 不带 ccache 编译
./build.sh --product-name rk3568
查看缓存统计
bash
ccache -s
输出示例:
Cacheable calls: 31469 / 31484 (99.95%)
Hits: 1180 / 31469 ( 3.75%) ← 命中次数
Direct: 801 / 1180 (67.88%) ← 直接命中(最快)
Preprocessed: 379 / 1180 (32.12%) ← 预处理后命中
Misses: 30289 / 31469 (96.25%) ← 未命中(需要真实编译)
Local storage:
Cache size (GB): 3.7 / 100.0 ( 3.75%) ← 缓存占用
常用命令
bash
# 查看统计信息
ccache -s
# 清空统计计数(不清缓存)
ccache -z
# 清空所有缓存
ccache -C
# 设置缓存大小上限
ccache -M 20G
# 查看当前配置
ccache -p
# 查看缓存目录
ccache -k cache_dir
3. 命中模式
ccache 有两种命中模式:
Direct 模式(最快)
源文件 hash + 编译参数 → 直接查找缓存
- 不需要运行预处理器
- 速度最快(微秒级)
- 要求源文件和所有头文件都未变化
Preprocessed 模式(次快)
源文件 → 预处理器展开 → 预处理结果 hash → 查找缓存
- 需要运行预处理器(毫秒级)
- 能处理
__TIME__、__DATE__等宏变化的情况 - 当 Direct 模式 miss 时自动回退到此模式
4. 性能对比
不同场景下的编译耗时(24 核 / 32GB,RK3568)
| 编译场景 | 不加 ccache | 加 ccache |
|---|---|---|
| 首次全量编译 | ~3 小时 | ~3 小时 10 分钟(略慢,写缓存开销) |
| 无改动重编 | ~2 小时 50 分钟 | ~10-20 分钟 |
| 改 1 个 .cpp | ~30-60 分钟 | ~2-5 分钟 |
| 改 1 个公共头文件 | ~2 小时 | ~30-60 分钟 |
| 切换分支后编译 | 全量重编 | 大部分命中缓存 |
| 架构切换(arm→arm64) | 全量重编 | 全量重编(hash 不同) |
命中率变化趋势
| 编译次数 | 预期命中率 | 说明 |
|---|---|---|
| 第 1 次 | 0-5% | 缓存为空,几乎全 miss |
| 第 2 次(无改动) | 95-99% | 几乎全部命中 |
| 第 2 次(小改动) | 90-95% | 仅受影响文件 miss |
| 第 2 次(改公共头文件) | 30-70% | 包含该头文件的所有源文件 miss |
5. 配置优化
缓存目录
bash
# 默认位置
~/.ccache/
# 自定义位置(建议放在 SSD 上)
export CCACHE_DIR=/data/ccache
推荐配置
bash
# 增大缓存上限(OpenHarmony 全量编译约需 5-10GB)
ccache -M 20G
# 启用压缩(节省磁盘,略增 CPU 开销)
ccache -o compression=true
ccache -o compression_level=3
# 设置最大文件数(避免小文件过多导致 inode 耗尽)
ccache -o max_files=0 # 0 表示不限制
配置文件
ccache 配置文件位于 ~/.config/ccache/ccache.conf:
ini
max_size = 20G
compression = true
compression_level = 3
6. 统计指标解读
Cacheable calls: 31469 / 31484 (99.95%)
| 指标 | 含义 |
|---|---|
| Cacheable calls | 可缓存的编译调用占比(接近 100% 说明 ccache 覆盖了几乎所有编译) |
| Hits | 命中次数(越高越好) |
| Direct hits | 直接命中(最快路径) |
| Preprocessed hits | 预处理后命中(次快) |
| Misses | 未命中(需要真实编译并写入缓存) |
| Uncacheable | 不可缓存的调用(如链接操作) |
| Errors | 编译错误次数(不是 ccache 的错,是源码编译失败) |
| Cache size | 当前缓存占用 / 上限 |
健康指标
| 指标 | 健康值 | 说明 |
|---|---|---|
| 命中率(非首次) | > 80% | 低于此值说明改动范围大或缓存被频繁淘汰 |
| Direct hit 占比 | > 60% | 低于此值可能有 __TIME__ 等宏干扰 |
| Cache size | < 80% 上限 | 接近上限时旧缓存会被淘汰 |
7. 缓存失效的常见原因
| 原因 | 影响范围 | 解决方法 |
|---|---|---|
| 修改公共头文件 | 所有包含该头文件的源文件 miss | 无法避免,这是正确行为 |
| 切换编译器版本 | 全部 miss | 无法避免 |
| 切换目标架构(arm↔arm64) | 全部 miss | 无法避免(编译参数不同) |
| 修改 GN 编译参数 | 受影响目标全部 miss | 尽量避免频繁改 gn-args |
| 缓存空间满被淘汰 | 最旧的缓存被删除 | 增大 max_size |
| 清空了 out 目录 | 不影响 ccache(缓存在 ~/.ccache) | 正常,ccache 仍有效 |
8. ccache 与 OpenHarmony 构建系统的集成
build.sh 中的实现
--ccache 参数会让构建系统在调用 clang 前插入 ccache:
# 不加 --ccache
clang --target=arm-linux-ohos -c foo.c -o foo.o
# 加 --ccache
ccache clang --target=arm-linux-ohos -c foo.c -o foo.o
GN 通过设置 cc_wrapper = "ccache" 实现这一点。
与 ninja 的配合
ninja 调度编译任务(并行)
→ 每个任务调用 ccache clang ...
→ ccache 查缓存
→ 命中:直接返回(不占 CPU)
→ 未命中:调用 clang 编译(占 CPU)
命中缓存的任务几乎不消耗 CPU,所以 --jobs=N 可以设得更高。
9. 最佳实践
日常开发
bash
# 始终加 --ccache
./build.sh --product-name rk3568 --ccache
# 编译前清零统计,编译后查看本次命中率
ccache -z
./build.sh --product-name rk3568 --ccache
ccache -s
清除编译产物但保留缓存
bash
# 只删 out 目录(ccache 不受影响)
rm -rf out/rk3568
# 重新编译时大部分会命中缓存
./build.sh --product-name rk3568 --ccache
CI/CD 环境
bash
# 持久化 ccache 目录到共享存储
export CCACHE_DIR=/shared/ccache/rk3568
# 设置较大缓存
ccache -M 50G
# 多分支共享缓存(相同源文件 + 相同参数 = 命中)
10. 什么时候不该用 ccache
| 场景 | 原因 |
|---|---|
| 磁盘空间极度紧张(< 10GB) | 缓存会占用额外空间 |
| 调试编译器本身的问题 | 需要看到真实的编译过程 |
| 编译器版本频繁切换 | 缓存基本不命中,纯浪费 IO |
| 只编译一次就丢弃的环境 | 写缓存的开销无法回收 |
11. 总结
| 结论 | 说明 |
|---|---|
默认就加 --ccache |
除极少数场景外,加 ccache 只有好处 |
| 首次编译略慢 | 写缓存有 5-10% 开销,可忽略 |
| 后续编译大幅加速 | 命中率 80%+ 时,编译时间缩短 5-10 倍 |
| 不影响编译正确性 | hash 严格匹配,不会返回错误的缓存 |
与 rm -rf out/ 兼容 |
缓存独立于编译产物目录 |