Skip to content

ccache加速编译

说明 AOSP 对 ccache 的受限兼容支持、正确接入条件、命中边界、风险和性能测量方法。

基于android-17.0.0_r1
AndroidAOSPccache构建性能

ccache加速编译 ​

Android 17 仍保留 ccache 接入代码,但它不是默认启用的官方加速方案。AOSP 不再提供 ccache prebuilt,并在 core/ccache.mk 中明确说明:旧版本曾出现不可复现结果和其他失败;在大型、多配置 构建中,如果缓存局部性不足,收益也可能不明显。

因此,这篇文章不会把 ccache 写成“设置两个变量就一定提速”。正确问题是:

  1. 当前构建是否真的经过 ccache wrapper;
  2. 重复工作是否属于 ccache 能缓存的 C/C++ 编译动作;
  3. 缓存命中是否足以抵消 hash、存储和清理成本;
  4. Ninja 增量、Siso 或 RBE 是否更适合当前工作负载。

Ninja构建系统 已说明构建图和增量判断。本篇只讨论编译器结果缓存。

本文面向已经理解 Ninja 增量、希望评估 ccache 是否值得接入当前工作负载的读者。边界是 Android 17 的 wrapper 门槛、缓存身份、正确性风险和测量方法,不把 ccache 内部实现或 RBE 服务端配置当成已证实 内容。源码主线由 core/ccache.mk、Soong 的 wrapper 环境到编译 action 消费;读者读完应能解释 何时生效、何时被跳过,并用可重复的冷/热缓存实验作出取舍。

前置的图与增量模型见 Ninja构建系统;若决定保留缓存并需要验证产物 如何部署,继续阅读 模块化编译。

1. Android17定位 ​

1.1 ccache ​

build/make/core/ccache.mk 的开头直接给出维护判断:

  • AOSP 不再提供 ccache prebuilt;
  • 旧 prebuilt 版本可能导致错误结果;
  • 多产品、多配置构建需要很高的缓存局部性和很大缓存;
  • 本地“不改代码的全量重建”可能受益,但此时 Ninja 增量通常更直接。

这不是彻底删除支持,而是把 ccache 变成使用者自行提供、评估和维护的兼容能力。

1.2 启用约束 ​

源码文件:build/make/core/ccache.mk

makefile
ifneq ($(CCACHE_EXEC),)
ifneq ($(filter-out false,$(USE_CCACHE)),)

  # 说明:只有执行路径存在,并且USE_CCACHE不是false时才设置wrapper。
  ifndef CC_WRAPPER
    CC_WRAPPER := $(CCACHE_EXEC)
  endif
  ifndef CXX_WRAPPER
    CXX_WRAPPER := $(CCACHE_EXEC)
  endif

endif
endif

只设置 USE_CCACHE=1 没有作用;只设置 CCACHE_EXEC 但显式 USE_CCACHE=false 也不会启用。

受控 Make 实验结果:

环境CC_WRAPPERCXX_WRAPPER
两者未设置空空
USE_CCACHE=false + executable空空
USE_CCACHE=1 + executableexecutableexecutable

2. 默认配置 ​

2.1 compilercheck ​

源码文件:build/make/core/ccache.mk

相关函数/类型:compiler check

makefile
CCACHE_COMPILERCHECK ?= content

默认 ccache 常用 compiler size 与 mtime 判断编译器身份,但源码 checkout 时间可能不同。AOSP 选择 content,以编译器内容参与判断,减少因 mtime 产生的假 miss。

2.2 sloppiness ​

源码文件:build/make/core/ccache.mk

相关函数/类型:sloppiness

makefile
CCACHE_SLOPPINESS := time_macros,include_file_mtime,file_macro

这些设置放宽部分输入判断以提高命中率,但意味着项目需要确认 __DATE__、__TIME__、 __FILE__ 和 include mtime 不承担关键语义。不要在不了解风险时继续增加 sloppiness。

2.3 缓存路径 ​

源码文件:build/make/core/ccache.mk

相关函数/类型:path normalization

makefile
CCACHE_BASEDIR := /
CCACHE_CPP2 := true

CCACHE_BASEDIR 用于减少预处理输出中的绝对路径差异,CCACHE_CPP2 是 Clang 兼容路径的一部分。

Soong 在 USE_CCACHE 为 true 时还增加:

源码文件:build/soong/cc/config/global.go

go
if ctx.Config().IsEnvTrue("USE_CCACHE") {
    // 说明:ccache包装Clang时可能留下未消费参数。
    flags = append(
        flags,
        "-Wno-unused-command-line-argument",
    )
}

2.4 环境传递 ​

Soong UI 的受控 Ninja 环境允许 CCACHE_COMPILERCHECK、CCACHE_SLOPPINESS、 CCACHE_BASEDIR、CCACHE_CPP2 和 CCACHE_DIR。这保证 wrapper 动作可以读取配置,同时避免开放 全部 shell 环境破坏增量正确性。

3. 缓存对象 ​

3.1 缓存对象入口 ​

ccache 的典型缓存对象是 C/C++ 编译器调用。它不直接缓存:

  • Soong 解析 Android.bp;
  • Kati 解析 Android.mk;
  • Java/Kotlin 编译,除非另有专用缓存体系;
  • 链接、资源打包、dex、R8、镜像生成;
  • Ninja 图生成;
  • adb 推送和设备验证。

如果耗时主要在链接、镜像、R8 或 Kati regen,ccache 命中率再高也不会显著缩短总时间。

3.2 命中身份 ​

一次编译要命中缓存,需要关键输入保持等价:

  • 编译器身份;
  • 预处理后源码和头文件;
  • 编译 flags、宏和 include path;
  • 目标架构与 variant;
  • 影响输出的环境;
  • 路径归一化结果。

3.3 增量缓存 ​

机制解决的问题
Ninja 增量输出仍有效时完全不执行动作
ccache动作需要执行,但相同编译结果曾经计算过

同一 out 目录中的日常修改通常优先受益于 Ninja 增量。清理 out、切换相似分支或重复配置时,ccache 才更可能补充收益。

4. 接入方式 ​

4.1 外部工具 ​

bash
export USE_CCACHE=1
export CCACHE_EXEC="$(command -v ccache)"
export CCACHE_DIR="/path/to/controlled-cache"

必须自行确认 ccache 版本、许可证、安装来源和磁盘策略。AOSP 不负责提供该二进制。

4.2 接入命令 ​

bash
showcommands <module> | grep ccache

NINJA_ARGS="-t commands <module>" m

如果编译命令没有经过 CCACHE_EXEC,不要继续讨论命中率,先检查环境变量是否在构建配置生成前 生效。

4.3 ccache --help ​

现代 ccache 常见命令包括:

bash
ccache --show-stats
ccache --zero-stats
ccache --show-config

不同版本参数可能变化,应以本机 ccache --help 为准。

5. 测量收益 ​

5.1 构建比较边界 ​

至少保持以下条件一致:

  • Product、Release、Variant;
  • 源码 commit;
  • 编译器和 host 环境;
  • 目标模块;
  • 并发数;
  • out 与 cache 策略。

5.2 推荐四组记录 ​

轮次outcache目的
A已有关闭Ninja 增量基线
B新建/清理冷缓存缓存写入成本
C新建/清理热缓存ccache 可复用收益
D已有热缓存判断日常增量是否真的受益

记录:

  • 总 wall time;
  • ccache hit/miss;
  • 编译动作数量;
  • Soong/Kati/Ninja 各阶段时间;
  • critical path;
  • cache 大小和淘汰量。

Soong UI 会生成 build.trace.gz、soong.log 和 verbose logs,可用于判断瓶颈是否真的在 C/C++ 编译。

5.3 命中率边界 ​

命中率受模块、分支、产品、编译器、路径和缓存容量影响。单个项目的高命中率不能外推到所有 AOSP 构建,也不能只用 hit ratio 代替总 wall time。

6. 正确性与维护风险 ​

6.1 错误命中 ​

过度放宽 sloppiness、未纳入 key 的环境变量或路径处理错误可能复用不正确对象。出现难以解释的 编译/运行差异时,应能快速关闭 ccache 并复现。

6.2 磁盘与淘汰 ​

AOSP 多架构、多 product 和多 variant 会快速扩大 cache。容量过小导致频繁淘汰,容量过大则增加 磁盘和维护成本。

6.3 缓存有效性 ​

构建应能在禁用个人缓存后复现。cache 只能加速,不应成为生成正确产物的隐含依赖。

7. RBE/Siso ​

build/make/core/rbe.mk 会把 rewrapper 追加到已有 CC_WRAPPER/CXX_WRAPPER,源码允许 ccache 与 远程执行组合。但双层缓存会增加 key、日志和故障定位复杂度。

方案适用条件主要限制
Ninja 增量同一工作树与 out 的日常构建清理 out 后不能复用
ccache重复 C/C++ 编译且局部性高不覆盖全部构建阶段
Siso/RBE有远程 worker 与共享 cache 基础设施配置、认证和服务成本

8. 常见问题 ​

8.1 USE_CCACHE ​

检查 CCACHE_EXEC 是否为可执行路径,并检查实际编译命令是否包含 wrapper。

8.2 命中率很低 ​

检查编译器、flags、路径、product/variant、源码分支和 cache 淘汰。先确认工作负载确实重复。

8.3 命中率变化 ​

瓶颈可能在 Soong/Kati、链接、打包、镜像或 critical path。查看 build trace。

8.4 异常产物 ​

关闭 ccache、清理目标产物并复现。保留 ccache stats、配置和编译命令,避免只删除整个源码树。

9. 基准结果 ​

ccache 是否值得长期启用,应由同一工作负载的冷、热缓存对比决定,而不是由“命中率看起来很高”决定:

观察更可能的结论
热缓存 wall time 明显下降,C/C++ action 占主要路径ccache 对当前工作负载有实际价值
hit ratio 高,但总时间几乎不变瓶颈在 Soong/Kati、链接、打包、镜像或其他 critical path
冷缓存明显变慢且热缓存收益有限hash、存储和淘汰成本超过收益
切换 product/variant 后大量 miss缓存局部性不足,需要重新评估容量和适用范围
禁用缓存后产物或行为变化正确性优先,检查 key、sloppiness 和缓存污染

评估时保持 product、release、variant、源码 commit、并行度和目标一致,同时记录 wall time、critical path、编译 action 数、cache stats 与缓存容量变化。只有当收益可重复、禁用缓存仍可复现正确构建, 并且维护成本低于 Siso/RBE 或规则优化方案时,ccache 才适合作为当前环境的长期配置。

10. 缓存测试 ​

先在同一 product/release/variant 下执行三次配置断言:未设置 CCACHE_EXEC、设置但 USE_CCACHE=false、两者同时设置。断言前两种 CC_WRAPPER/CXX_WRAPPER 为空,第三种包含同一 可执行路径;这证明 gate 和环境传播,不证明任何 action 命中缓存。

再用相同源码和并行度完成冷缓存与热缓存两次构建,记录 wall time、critical path、编译 action 数、 hit/miss 和产物 hash。热缓存变快只能证明该工作负载受益;禁用 ccache 后 hash 与行为仍一致,才 能覆盖正确性回归。切换 product 或 variant 后的 miss 不能被解释成 ccache 失效,而应归因于缓存 身份边界。