sccache 架构全解:哈希决策、Direct Mode 预处理器缓存与双执行模式剖析 📅 发布时间:2026/9/16 17:15:01 👁 浏览次数: sccache 架构全解哈希决策、Direct Mode 预处理器缓存与双执行模式剖析【免费下载链接】sccacheSccache is a ccache-like tool. It is used as a compiler wrapper and avoids compilation when possible. Sccache has the capability to utilize caching in remote storage environments, including various cloud storage options, or alternatively, in local storage.项目地址: https://gitcode.com/GitHub_Trending/sc/sccachesccache 是一个 ccache 风格的编译器缓存工具通过包装编译器调用来复用缓存结果。本文以 docs/Architecture.md 为主线结合仓库源码src/commands.rs、src/protocol.rs、src/cache/ipc_storage.rs、src/compiler/preprocessor_cache.rs 等展开系统讲解 sccache 高层架构一次编译调用如何通过哈希决定“直接编译”还是“复用缓存”、C/C 的 Direct Mode预处理器缓存如何跳过预处理以及服务端模式与客户端模式SCCACHE_CLIENT_SIDE两种执行模式的分工与演进方向。读完本文你将能理解 sccache 的缓存命中链路、两种模式的异同与配置方式并能读懂其核心源码。一、整体架构一次编译调用的决策流程sccache 的核心决策模型非常简单把一次编译的所有关键输入折叠成一个哈希键用该键去存储后端本地磁盘、S3、Redis、Memcached、GCS 等查询缓存命中则直接下载并复用结果未命中则真正执行编译并把产物上传回存储。sccache 高层决策流程图下面的流程图源自 docs/Architecture.md 原文直观地展示了这一决策过程1.1 哈希输入什么会被折叠进缓存键上图中的蓝色输入节点——环境变量、编译器二进制、编译器参数、文件内容——共同决定了缓存键的取值。任何一项发生变化都会导致哈希变化从而产生缓存未命中。具体到语言实现docs/Caching.md 给出了详细清单Rust 编译对每个编译文件生成 blake3 摘要同时把 rustc 可执行文件路径、Host triple、rustc sysroot 路径、$sysroot/lib下所有共享库的摘要、rlib 依赖dist-client 场景以及解析后的 rustc 参数一并纳入哈希。C/C 编译哈希基于预处理后文件-E产物的 blake3 摘要并额外纳入编译器二进制哈希、汇编器二进制哈希与版本、编程语言、语言编译标志、依赖生成参数、预处理参数、架构参数、需要哈希内容的额外文件、覆盖率/剖析数据标志、颜色模式以及环境变量等。C/C 预处理器即下文 Direct Mode在 C/C 编译器键的基础上额外加入输入文件路径与输入文件内容哈希。1.2 命中与未命中命中Storage 返回 yes从存储下载缓存的目标产物object 文件跳过编译直接返回。未命中Storage 返回 no真正执行编译Compile随后把产物上传Upload回存储供下次复用。需要说明的是上述缓存逻辑无论运行在哪种执行模式下都完全一致区别只在于“由哪个进程实际运行编译器、谁与存储后端对话”——这正是本文第三部分要展开的两大执行模式。二、Direct Mode预处理器缓存C/C 专属优化对于 C/Csccache 还提供一种额外的缓存缓存预处理结果本身从而让一次缓存查找可以完全跳过预处理步骤。这一设计灵感来自 ccache 的 direct mode其细节记录在 docs/Local.md 中。2.1 工作方式在计算 object 缓存键之前sccache 会先以一个“预处理器缓存条目”作为键去查询存储。该条目的键由源文件路径 内容与预处理参数共同决定条目中记录着源文件所包含include的每一个文件。逻辑如下若条目存在且其中记录的所有被包含文件都未发生变化则复用该条目完全跳过预处理否则正常执行预处理并写回一条新的条目。该步骤默认开启对应流程图中蓝色区域但在以下场景会被跳过非 C/C 语言显式禁用见下文的SCCACHE_DIRECT/use_preprocessor_cache_mode命令行中存在-Wp,*或-Xpreprocessor等标志在 src/compiler/c.rs 中体现为too_hard_for_preprocessor_cache_mode判定。2.2 源码实现PreprocessorCacheEntrysrc/compiler/preprocessor_cache.rs 中定义了核心数据结构PreprocessorCacheEntrysrc/compiler/preprocessor_cache.rs#L49-L59results: BTreeMapString, VecIncludeEntry按 object 缓存键result key组织的一组被包含文件清单。之所以允许一个源文件对应多个 result是因为“头文件变了但源文件没变”时旧条目不能立刻作废。IncludeEntrysrc/compiler/preprocessor_cache.rs#L446-L459记录每个被包含文件的绝对路径、内容摘要digest、文件大小、mtime 与 ctime。命中判定lookup_result_digestsrc/compiler/preprocessor_cache.rs#L176-L191会按“最新优先”顺序遍历 results对每个条目调用result_matches先比较文件大小再根据配置比较 mtime/ctime最后回退到内容摘要比对。源码还做了健壮性兜底条目总数上限MAX_PREPROCESSOR_CACHE_ENTRIES 100、被包含文件信息上限MAX_PREPROCESSOR_CACHE_FILE_INFO_ENTRIES 10000src/compiler/preprocessor_cache.rs#L45-L47超限时直接清空重建防止条目无限膨胀拖慢查找。2.3 时间宏的特殊处理预处理结果可能受到__DATE__、__TIME__、__TIMESTAMP__等时间宏的影响因此 src/compiler/preprocessor_cache.rs 对它们做了专门处理扫描文件时若发现__TIME__直接禁用预处理器缓存模式因为同一秒内的命中概率极低且结果会过期若发现__DATE__/__TIMESTAMP__则把日期/时间信息折叠进摘要并考虑SOURCE_DATE_EPOCH环境变量的影响。相关测试见该文件末尾的test_find_time_macros_*系列用例。2.4 相关配置项docs/Local.md 中给出了预处理器缓存模式的可配置项use_preprocessor_cache_mode默认true是否启用预处理器缓存模式单次调用可用环境变量SCCACHE_DIRECT取true/on/1或false/off/0覆盖。ignore_time_macros默认false为true时忽略源码中的__DATE__、__TIME__、__TIMESTAMP__可加速缓存命中但可能产生过期结果。skip_system_headers默认false为true时预处理器缓存只把系统头文件的路径计入缓存键而忽略其内容。三、CLI 与守护进程两种执行模式的分工sccache 被拆分成两个进程docs/Architecture.md短生命周期的 CLI 进程每次编译器调用例如make -jN为每个编译任务都会启动一个负责解析命令行、与守护进程通信长生命周期的守护进程daemon常驻后台持有存储后端与累计统计信息。两者通过本地 IPC 连接通信。连接地址默认端口为4226常量DEFAULT_PORT见 src/commands.rs#L51可通过SCCACHE_SERVER_PORT环境变量覆盖在 Unix 上还可通过SCCACHE_SERVER_UDS指定 Unix 域套接字src/commands.rs#L57-L69。当 CLI 尝试连接而端口无服务时会通过connect_or_start_serversrc/commands.rs#L315-L352自动以SCCACHE_START_SERVER1重新拉起守护进程并等待其启动就绪默认超时 10 秒SERVER_STARTUP_TIMEOUTsrc/commands.rs#L54。“谁跑编译、谁访问存储”有两种划分方式即下文的服务端模式与客户端模式。两种模式共享同一套缓存决策逻辑差异仅在进程职责。四、服务端模式默认服务端模式是当前默认的执行方式。CLI 把整个编译任务转发给守护进程守护进程执行 Direct Mode 的预处理器缓存查找、计算哈希、查询 object 缓存未命中时运行编译器并把结果写入存储。编译产物文件如 object 文件由守护进程直接写盘回传给 CLI 的只有 stdout / stderr 输出流与退出码。4.1 源码佐证Compile 请求链路在源码层面CLI 侧通过do_compilesrc/commands.rs#L633-L654把解析后的命令包装成Request::Compile发给守护进程守护进程端由handle_compilesrc/server.rs#L1167-L1178接收并执行完整的缓存决策流水线。协议层的数据结构定义在 src/protocol.rsCompile结构体src/protocol.rs#L101-L110承载编译器可执行文件路径、当前工作目录、命令行参数与环境变量CompileFinishedsrc/protocol.rs#L85-L97携带编译进程的返回码retcode、终止信号signal、stdout、stderr 以及颜色模式color_mode这正是上图中“只有输出流与退出码回传 CLI”的实现载体。值得注意的是服务端模式下若守护进程在发送CompileStarted后、发送CompileFinished前意外断开CLI 会回退到本地直接执行编译见 src/commands.rs#L517-L569 及同文件末尾的test_handle_compile_response_disconnect_falls_back_to_local测试保证构建不因守护进程崩溃而失败。五、客户端模式SCCACHE_CLIENT_SIDE客户端模式下编译流水线跑在 CLI 进程内部守护进程只作为访问存储后端的“共享网关”同时也是聚合统计信息的地方。5.1 执行流程启动时 CLI 先做一次性的StorageHandshake从守护进程拉取缓存元数据缓存模式、最大尺寸、basedirs、预处理器缓存配置随后在本地执行与守护进程完全相同的编译流水线含 Direct Mode并把每一次独立的缓存操作通过 IPC 逐条转发给守护进程5.2 关键机制StorageGetPath 与原始字节回退上图中StorageGetPath让 CLI 在存储后端暴露本地路径时直接读取磁盘上的缓存条目省去一次网络/序列化开销对于不暴露本地路径的后端如 S3、Redis 等CLI 回退为通过StorageGetRaw拉取原始字节。Direct Mode 的条目则通过StorageGetPreprocessorEntry/StoragePutPreprocessorEntry交换。由于客户端模式下每个 CLI 进程各自累积统计信息进程退出前会通过RecordStats把增量统计冲刷给守护进程合并。5.3 源码佐证IpcStorage 与协议客户端模式的核心实现是IpcStoragesrc/cache/ipc_storage.rs#L36-L68。它实现了与所有存储后端相同的Storagetraitsrc/cache/cache.rs#L75因此编译流水线的其余部分无需任何改动——这正是该设计的关键巧妙之处后端无关性通过 trait 抽象天然获得。IpcStorage::getsrc/cache/ipc_storage.rs#L72-L105体现了完整的回退链先尝试StorageGetPathGetPathResult::Found直接打开文件、Miss返回未命中、Unsupported回退再回退StorageGetRaw拉取字节。而协议层的请求/响应定义在 src/protocol.rs#L10-L38 与 src/protocol.rs#L42-L71StorageHandshakeInfosrc/protocol.rs#L113-L121则完整携带了握手所需的缓存元数据。守护进程端对这些存储 RPC 的分发逻辑在 src/server.rs#L904-L981StorageHandshake从当前存储实例收集 location、cache_type_name、basedirs、预处理器缓存配置、缓存模式与最大尺寸StorageGetPath/StorageGetRaw/StoragePutRaw/StorageGetPreprocessorEntry/StoragePutPreprocessorEntry逐一映射到对应存储方法RecordStats将增量合并进守护进程的统计。CLI 侧的入口分支位于 src/commands.rs#L916-L939当config.client_side_mode为真时创建仅含 2 个工作线程的 runtime一个用于预处理/编译一个用于 IPC调用do_compile_client_sidesrc/commands.rs#L662-L716在进程内构造IpcStorage与SccacheService并直接执行compile_directsrc/server.rs#L1184-L1199最后把take_stats得到的统计增量经RecordStats回传守护进程。5.4 开启方式与互斥条件客户端模式通过环境变量SCCACHE_CLIENT_SIDE或配置文件中的client_side_mode键启用docs/Configuration.md#L195 明确其为推荐模式并预期未来成为唯一受支持的配置服务端模式最终会被移除。配置解析在 src/config.rs#L1279环境变量与 src/config.rs#L802文件配置FileConfig字段完成环境变量优先级高于文件配置src/config.rs#L1412-L1419。该设置目前与以下两项互斥任一存在时设置会被忽略、sccache 回退到服务端模式互斥项原因错误日志SCCACHE_ERROR_LOG客户端模式下日志总是写到 stderr多个并发的 CLI 进程会争用同一个日志文件产生写竞争见 src/config.rs#L1413-L1417 注释触发条件是设置了SCCACHE_LOG等日志环境变量分布式编译配置了 scheduler URL客户端模式与分布式编译不兼容触发条件是dist.scheduler_url已配置src/config.rs#L14195.5 两种执行模式对比维度服务端模式默认客户端模式SCCACHE_CLIENT_SIDE编译执行位置守护进程CLI 进程存储访问方式守护进程直接访问CLI 经IpcStorage逐条 RPC 转发预处理器缓存查找守护进程执行CLI 本地执行经StorageGet/PutPreprocessorEntry转发统计信息守护进程统一维护CLI 各自累积退出前RecordStats合并与SCCACHE_ERROR_LOG/ 分布式编译兼容互斥存在则回退服务端模式演进方向计划移除未来唯一支持的配置六、总结一次调用的完整心智模型把全文串起来sccache 对sccache cc -c foo.c的处理可以归纳为进程拆分短命 CLI 常驻 daemon本地 IPC 通信默认端口 4226daemon 未启动时自动拉起Direct Mode 优先C/C 且未禁用以“源文件路径 内容 预处理参数”查预处理器缓存条目命中且被包含文件未变则跳过预处理直接得到 object 缓存键object 缓存决策以环境变量、编译器二进制、参数、文件内容预处理产物等折叠出的哈希查询存储命中即下载复用未命中则编译并上传执行模式默认由 daemon 承担全部工作服务端模式启用SCCACHE_CLIENT_SIDE后编译流水线移入 CLIdaemon 退化为存储网关IpcStorage通过统一的Storagetrait 保证流水线代码零改动。如果希望进一步深入哈希生成细节与 Direct Mode 的本地实现可继续阅读 docs/Caching.md 与 docs/Local.md各存储后端本地、S3、Redis、Memcached、GCS 等的实现与配置则分别记录在 docs/S3.md、docs/Redis.md、docs/Memcached.md、docs/Gcs.md 等文档中其后端模块均位于 src/cache 目录之下。【免费下载链接】sccacheSccache is a ccache-like tool. It is used as a compiler wrapper and avoids compilation when possible. Sccache has the capability to utilize caching in remote storage environments, including various cloud storage options, or alternatively, in local storage.项目地址: https://gitcode.com/GitHub_Trending/sc/sccache创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考