为何ccache在Make、Ninja等构建系统已处理依赖时仍能生效?
Make、Ninja这类构建系统的依赖管理基于文件时间戳或存在性,而ccache是基于**编译输入的完整内容(源码、编译器参数、所有头文件内容等)**做缓存,两者解决的是不同维度的问题。ccache能覆盖很多构建系统处理不了的真实场景,以下是典型案例:
1. 同一源码文件被多参数/多场景编译
大型项目中,同一个源码文件常需要以不同编译参数编译,比如:
- 同一个
util.c,主程序编译用-O2 -DNDEBUG,单元测试编译用-O0 -g -DUNIT_TEST - 跨平台项目中,同一个
algorithm.c需要为Windows、Linux、Android分别编译,对应不同的架构、优化宏定义
构建系统会把这些视为完全独立的编译任务,每次都要从头编译。但ccache会根据源码内容+编译参数的组合哈希值缓存结果,下次遇到相同组合时直接复用缓存,无需重复编译。比如我们的跨平台SDK项目,三个平台的核心算法编译任务,初次构建后后续再触发时直接命中缓存,单模块编译时间从10分钟降到1分钟以内。
2. 构建系统依赖解析不完整或误触发
项目中头文件依赖关系复杂时,构建系统的依赖生成(比如Make的-MMD)可能出现问题:
- 漏掉嵌套引入的间接头文件,导致头文件修改后未触发依赖文件重编译
- 自动生成的头文件(如
config.h)每次构建都会更新时间戳,但内容无变化,触发大量无意义重编译
ccache会哈希所有参与编译的内容(包括所有头文件的实际内容):
- 若头文件内容真的修改,哪怕构建系统没检测到,ccache也会重新编译
- 若只是头文件时间戳变化但内容一致,ccache直接返回缓存结果,避免无意义编译。比如我们的项目中,自动生成的
config.h每次都会更新,但ccache让依赖它的数百个文件不用每次都重编译,节省了20+分钟的构建时间。
3. 清理构建或切换构建配置后的快速重建
执行make clean && make、切换Debug/Release配置,或者在CI环境中使用全新容器构建时,构建系统会从头编译所有文件,但ccache保留着之前的编译缓存。只要源码、编译参数无变化,就能直接复用缓存,把全量构建时间从几十分钟压缩到几分钟。比如我们的CI构建,启用ccache后,全量构建时间从40分钟降到5分钟左右。
4. 多人协作或多节点共享缓存
团队协作时,不同开发者或分支的相同源码文件,只要编译参数一致,就能复用ccache缓存:
- 开发者A在feature分支编译了
network.c,开发者B的main分支中network.c内容完全一致,编译时直接命中A的缓存 - 把ccache缓存挂载到共享存储,所有CI节点都能复用缓存,进一步减少重复编译的开销
总结来说,ccache的核心是通过内容哈希实现编译结果的精准复用,填补了构建系统基于文件元信息管理的短板,在实际项目中能大幅减少不必要的编译时间,绝非仅用于“改了又改回”这类边缘场景。
内容的提问来源于stack exchange,提问作者liuzikai

