ccache在云端CI中基本失效,因其仅支持本地缓存、对路径和编译选项敏感、无法跨机器共享且不兼容-fdebug-prefix-map等现代选项;sccache需配置远程存储并禁用非确定性选项才能真正生效。

用 ccache 或 sccache 做云端 C++ 编译加速,关键不在于“装上就快”,而在于缓存是否真正被命中、是否跨机器共享、以及是否绕过了编译器本身的不可缓存行为。单机 ccache 在 CI/CD 中几乎无效;真正起效的是带远程存储的 sccache,且必须正确配置编译环境使其生成可复用的哈希键。
为什么 ccache 在云端 CI 中基本失效
ccache 默认只做本地文件系统缓存,且对绝对路径、临时目录、宏定义顺序极度敏感。CI 环境中每次构建都在全新容器或虚拟机里运行,CCACHE_BASEDIR 若未统一,所有缓存键都会错乱;更致命的是,它无法处理 -fdebug-prefix-map、-frecord-gcc-switches 等现代构建系统默认启用的选项,导致 99% 的编译单元无法命中缓存。
- CI 构建路径通常是随机的(如
/tmp/xyz123/build),ccache把路径嵌入哈希,缓存完全不复用 - Clang/GCC 新版本默认加
-frecord-gcc-switches,记录完整命令行(含绝对路径),进一步破坏一致性 - 没有内置 S3/GCS/Redis 后端,无法实现多 runner 共享缓存
sccache 必须启用远程存储并禁用非确定性选项
sccache 原生支持 S3、GCS、Redis 和 HTTP 后端,但默认仍走本地磁盘。要让它在云端生效,必须显式配置远程存储,并同步调整编译命令以消除哈希扰动。
- 启动时必须指定后端:例如
sccache --start-server --cache-dir /dev/shm --s3-bucket my-ccache-bucket --s3-region us-east-1 - 编译前导出环境变量:
export SCCACHE_BUCKET=my-ccache-bucket、SCCACHE_REGION=us-east-1,且确保 AWS 凭据可用(推荐使用 IRSA 或 workload identity) - 在 CMake 中强制关闭非确定性选项:
add_compile_options(-fdebug-prefix-map=/tmp/build= -frecord-gcc-switches),其中/tmp/build需与 CI 实际工作目录一致 - 避免使用
-g以外的调试格式(如-gdwarf-5),某些版本sccache对新版 DWARF 支持不稳定
如何验证缓存是否真实生效
别只看 sccache --show-stats 里的 hit rate——它可能显示 80%,但全是本地旧缓存。真正要看的是远程后端的实际 GET/PUT 请求量,以及编译日志中每条命令前是否出现 [sccache] Cache hit。
立即学习“C++免费学习笔记(深入)”;
- 在 CI 脚本中加入:
sccache --show-stats | grep -E "(Cache hits|Cache misses)",并在每次构建后清空本地缓存(sccache --stop-server && rm -rf /tmp/sccache)来排除本地干扰 - 检查 S3 bucket 是否有新 object 写入:对象名形如
ff/ff1234abcd.../output.o,若连续多次构建无新增,则说明没上传——大概率是编译命令哈希不一致 - 开启 debug 日志:
SCCACHE_LOG=debug sccache --dist-status,观察是否报Failed to get cache entry: key not found,这通常意味着预处理器输出不一致(比如头文件时间戳、__DATE__宏)
分布式编译(distcc)和 sccache 不要混用
sccache 自带分布式编译能力(通过 --dist-server),但它和传统 distcc 是互斥的。若你在 CMake 中设置了 CMAKE_CXX_COMPILER_LAUNCHER=distcc,sccache 就不会介入编译流程,等于白配。
- 只用
sccache就够了:它既做缓存又做分发,无需额外部署distcc集群 - 确保
CXX指向sccache:例如export CXX="sccache clang++",而不是export CXX="clang++"+ 单独 launcher - 如果已有
distcc流程,必须彻底移除所有distcc相关环境变量(DISTCC_HOSTS、CCACHE_PREFIX=distcc)
最常被忽略的一点:C++ 模板实例化和宏展开高度依赖头文件内容与顺序,哪怕一个头文件里多了一个空格,哈希就全变。所以不要指望第一次构建就能高命中——先跑通一次完整构建,让缓存“热起来”,再观察后续增量构建的收益。否则你看到的永远是 0% hit。











