news 2026/9/16 15:29:40

sccache 缓存机制详解:哈希键的生成原理与预处理器缓存模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sccache 缓存机制详解:哈希键的生成原理与预处理器缓存模式

sccache 缓存机制详解:哈希键的生成原理与预处理器缓存模式

【免费下载链接】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

sccache 是一款类似 ccache 的编译缓存工具,通过包装编译器来避免不必要的重复编译。其核心工作方式是:在访问存储(本地磁盘或各类云存储)之前,先对本次编译的输入计算一组哈希,只有当哈希完全一致时才判定缓存命中。本文基于 docs/Caching.md 展开,结合仓库源码(src/compiler/c.rs、src/compiler/rust.rs、src/compiler/preprocessor_cache.rs)与 docs/Local.md 中的本地缓存配置,系统讲解 Rust 与 C/C++ 两类编译的哈希键构成、多架构编译的处理方式,以及可大幅提速的预处理器缓存模式(preprocessor cache mode),帮助读者理解缓存命中率背后的决定因素,并学会通过环境变量与配置文件精确控制缓存行为。

缓存命中的前提:计算一致的哈希键

sccache 判断"存储中是否已有我们需要的产物"的唯一依据是哈希键(hash key)。由于编译配置(编译器版本、参数)和环境(环境变量、头文件搜索路径)都会影响编译结果,哈希计算必须把这些要素全部纳入,否则可能错误复用产物导致编译结果失真。因此 sccache 对不同语言分别设计了独立的哈希键生成逻辑,核心实现在CompilerHashertrait 的generate_hash_key方法中:

  • Rust:见 src/compiler/rust.rs 中RustHasher的实现;
  • C/C++:见 src/compiler/c.rs 中CCompilerHasher的实现。

下面分别说明两类哈希键的构成。

Rust 编译的哈希键

对每个 Rust 文件的编译,sccache 会为每个被编译的文件生成一个 blake3 摘要(digest),同时并行地把以下要素计入哈希:

  • rustc 可执行文件的路径(注意是解析 rustup 代理之后真正的 rustc 路径,见 src/compiler/rust.rs);
  • 该 rustc 的 host triple(例如x86_64-unknown-linux-gnu),它从rustc -vV输出中解析得到;
  • rustc 的 sysroot 路径(通过rustc --print=sysroot获取);
  • rustc 的$sysroot/lib下所有共享库的摘要(Windows 上为$sysroot/bin),这些共享库的 digest 在Rust::new中通过hash_all一次性计算并缓存,见 src/compiler/rust.rs;
  • 一个共享的、带缓存的 rlib 依赖读取器rlib_dep_reader,仅dist-client特性下启用),用于分布式编译场景;
  • 本次 rustc 调用的已解析参数(parsed arguments)。

完整哈希输入列表定义在 src/compiler/rust.rs,其顺序为:

  1. 一个缓存版本号CACHE_VERSION(当前为b"6",见 src/compiler/rust.rs),改动哈希输入时必须同步递增;
  2. 所有compiler_shlibs_digests(sysroot 共享库摘要);
  3. 完整命令行(其中--extern-L--check-cfg--out-dir--diagnostic-width等包含无关路径或不确定顺序的参数会被剔除;--cfg被排序后追加;指向 JSON 文件的--target参数内容单独参与哈希);
  4. 所有源文件的摘要(通过rustc --emit=dep-info输出的依赖清单获得,见get_source_files_and_env_deps);
  5. 命令行列出的所有--extern依赖库的摘要;
  6. 命令行列出的所有静态库(staticlib)的摘要;
  7. --target指向的 JSON 文件内容的摘要;
  8. 环境变量:包括 rustc dep-info 输出中列出的所有# env-dep:变量,以及所有CARGO_开头的环境变量(但会剔除CARGO_MAKEFLAGSCARGO_REGISTRIES_*token、CARGO_BUILD_JOBSCARGO_ENCODED_RUSTFLAGS等不可缓存或已冗余的项);
  9. 编译时的当前工作目录(cwd);
  10. rustc 的版本字符串(完整rustc -vV输出,防止版本升级后复用旧产物)。

此外,sccache 还会额外计算一个weak_toolchain_key(由编译器可执行文件路径 + 其摘要构成),用于规避"编译器二进制是符号链接、摘要相同但真实路径不同"带来的工具链定位错误,见 src/compiler/c.rs 与 src/compiler/c.rs 中的注释说明。

C/C++ 编译的哈希键

对于 C/C++,sccache 的做法是先对源文件做预处理(preprocess),再对预处理结果(-E的输出)计算 blake3 摘要。预处理是 C/C++ 编译中最昂贵的环节之一,把它作为哈希键的主体意味着:只要预处理结果一致,编译产物就可复用。

除预处理输出外,C/C++ 哈希键还计入以下要素(对应CCompilerHasher::generate_hash_key的实现,见 src/compiler/c.rs):

  • 编译器二进制的哈希:实际计算时还会混入编译器报告的版本号(如果编译器能报告版本的话),见CCompiler::new中的executable_digest计算(src/compiler/c.rs);
  • 汇编器二进制的哈希及其报告的版本(如果编译器会把汇编工作交给外部汇编器):GCC 总是如此,clang 仅在指定-fno-integrated-as时如此。汇编器摘要由CCompilerImpl::assembler_digest提供;
  • 编程语言(C/C++/Objective-C 等)及编译该语言所需的标志(如-x c++);
  • 依赖生成目标文件depfile);
  • 依赖生成相关的命令行参数dependency_args);
  • 预处理器相关命令行参数preprocessor_argscommon_args);
  • 指定编译架构的命令行参数arch_args);
  • 需要哈希内容的额外文件extra_hash_files,通过hash_all并行计算);
  • 是否生成 profiling 或 coverage 数据profile_generate);
  • 颜色模式color_mode);
  • 环境变量env_vars,在HashKeyParams::with_env_vars中计入)。

这些参数在解析阶段被分类存储于ParsedArguments结构体(src/compiler/c.rs),生成哈希键时,预处理器参数、架构参数与通用参数会被合并为preprocessor_and_arch_args,再通过HashKeyParams与预处理输出共同计算最终键值,见 src/compiler/c.rs。

多架构编译与SCCACHE_CACHE_MULTIARCH

当一条编译命令指定了多个-arch标志时(常见于 macOS 的通用二进制编译),sccache 需要把这些-arch标志重写为对应的预处理器宏,才能对文件做统一的预处理,例如把-arch x86_64重写为-D__X86_64__=1

该行为由环境变量SCCACHE_CACHE_MULTIARCH控制,默认关闭(未设置即视为关闭),因为重写并不能保证在所有情况下都正确:

  • 未设置SCCACHE_CACHE_MULTIARCH时,遇到多个不同的-arch会直接判定为不可缓存,报错信息为"multiple different -arch, and SCCACHE_CACHE_MULTIARCH not set",见 src/compiler/gcc.rs 与 src/compiler/gcc.rs;
  • 设置后,架构参数会参与预处理并进入哈希键;仓库测试 src/compiler/gcc.rs 验证了开启该变量时多-arch参数可被正确重写与缓存,src/compiler/gcc.rs 则验证了未开启时的禁用行为。

辅助语言(CUDA/HIP 等)的特殊处理

在 src/compiler/c.rs 中可以看到,对 HIP 等依赖位码库的编译,sccache 还会根据--rocm-path--hip-device-lib-path参数以及ROCM_PATHHIP_DEVICE_LIB_PATH环境变量解析 HIP 设备库路径,并将相关文件计入哈希,确保依赖的位码库变化时缓存能正确失效。

C/C++ 预处理器缓存模式:额外的缓存键

在"预处理器缓存模式"(preprocessor cache mode)下,sccache 会把预处理器的输出本身也缓存起来。这样当源码与所有头文件都未变化时,可以直接跳过预处理这一最耗时的环节,从缓存中拿到编译产物,大幅缩短缓存命中的响应时间。该模式详细介绍见 docs/Local.md 的 "Preprocessor cache mode" 一节,其设计灵感来自 ccache 的 direct mode。

预处理器缓存键与常规 C/C++ 编译器哈希键非常接近,但额外加入两个要素(对应preprocessor_cache_entry_hash_key的实现,见 src/compiler/preprocessor_cache.rs):

  • 输入文件的路径(并依据配置做 basedir 剥离后再哈希,避免不同检出目录间误命中);
  • 输入文件的哈希(内容摘要)。

该函数还会把一组与头文件搜索相关的环境变量计入缓存键,定义在CACHED_ENV_VARS(src/compiler/preprocessor_cache.rs),包括:

环境变量作用
SCCACHE_C_CUSTOM_CACHE_BUSTER用户自定义的"缓存破坏器",设置为不同值即可阻止不同调用之间复用缓存
CPATH头文件搜索路径(影响包含解析结果)
C_INCLUDE_PATHC 头文件搜索路径
CPLUS_INCLUDE_PATHC++ 头文件搜索路径
OBJC_INCLUDE_PATHObjective-C 头文件搜索路径
OBJCPLUS_INCLUDE_PATHObjective-C++ 头文件搜索路径

预处理器缓存的数据结构

预处理器缓存条目PreprocessorCacheEntry(src/compiler/preprocessor_cache.rs)本质上是一张"结果键 → 包含文件列表"的映射表,磁盘格式为 1 字节版本号(FORMAT_VERSION = 1)+ bincode 序列化。每个IncludeEntry记录被包含文件的绝对路径、内容摘要、文件大小以及(满足条件时记录的)mtime/ctime。

命中判定lookup_result_digest从最新的结果开始倒序遍历,逐一核对所有被包含文件是否仍然存在且未变化(见result_matches,src/compiler/preprocessor_cache.rs)。为了防止条目无限膨胀,源码还设置了两个上限:单个条目最多保留MAX_PREPROCESSOR_CACHE_ENTRIES = 100个结果,全部被包含文件条目上限为 10000,超限时直接清空重建。

预处理器缓存模式何时被禁用

预处理器缓存模式在以下任一情况下会被自动关闭:

  • 编译的不是 C 或 C++(以及无需 C 预处理的语言,见 src/compiler/c.rs);
  • 配置项use_preprocessor_cache_modefalse
  • 使用的不是 GCC 或 Clang
  • 缓存存储不是本地磁盘
  • 命令行中存在-MP-Xpreprocessor-Wp,中任何一个选项(在参数解析阶段被标记为too_hard_for_preprocessor_cache_mode,见 src/compiler/c.rs;-Xpreprocessor-Wp,*也会让键值计算直接返回None禁用,见 src/compiler/preprocessor_cache.rs);
  • 某个头文件的修改时间过新(晚于编译开始时间,为避免竞争条件,见add_result中对 mtime/ctime 的判定,src/compiler/preprocessor_cache.rs);
  • 源码中出现__DATE____TIME____TIMESTAMP__时间宏——这些宏的展开值取决于外部时间因素,可能使预处理器结果不稳定(检测逻辑见Digest::reader_sync_time_macros,在 src/compiler/preprocessor_cache.rs 中调用;__TIME__命中时会直接返回None禁用该模式)。

可能静默产生过期结果的情况

以下场景下预处理器缓存可能静默返回过期的结果,需要使用者知晓:

  • 某次编译时一个头文件尚不存在,但如果它存在就会被包含;sccache 无从知晓这类"潜在包含",因此之后该头文件被创建时缓存不会失效;
  • 源码中使用了__TIME__等时间宏,同时开启了ignore_time_macros
  • 存在其他影响预处理结果、但 sccache 无法感知的外部因素。

预处理器缓存模式的配置项

以下配置项定义于PreprocessorCacheModeConfig(src/config.rs),默认值如下:

配置项默认值说明
use_preprocessor_cache_modetrue是否启用预处理器缓存模式。可通过环境变量SCCACHE_DIRECT对单次调用覆盖(true/on/1false/off/0),覆盖逻辑见 src/config.rs 与 src/compiler/c.rs
file_stat_matchesfalsefalse时仅通过哈希文件内容比较头文件是否变化;为true时改用"文件大小 + ctime + mtime"快速比对(配合下面的开关做更细粒度控制)
use_ctime_for_stattruetrue时使用 ctime(UNIX 上为状态变更时间,Windows 上为创建时间)判断文件是否变化;在需要受控回拨修改时间(backdate)的场景可考虑关闭
ignore_time_macrosfalsetrue时忽略源码中的__DATE____TIME____TIMESTAMP__。能提升预处理器缓存模式的命中速度,但可能产生过期结果
skip_system_headersfalsetrue时,预处理器缓存只为系统头文件记录路径(不缓存其内容),减少缓存体积与比对开销
hash_working_directorytruetrue时将当前工作目录加入缓存键,以区分不同目录下的编译。对应实现见 src/compiler/c.rs

注意:源码中PreprocessorCacheModeConfig::default()use_preprocessor_cache_mode字段为false,但实际启用时使用的是PreprocessorCacheModeConfig::activated()(将该项置为true),本地磁盘缓存的默认配置即调用activated(),因此本地缓存默认开启预处理器缓存模式,与文档描述一致。配置文件的书写位置见 docs/Configuration.md。

本地缓存的其它实用配置

结合 docs/Local.md,围绕缓存键之外的本地缓存行为还有以下要点:

  • 缓存目录:sccache 默认使用本地磁盘存储,可通过环境变量SCCACHE_DIR改变缓存位置。各平台默认位置为:Linux~/.cache/sccache、Windows%LOCALAPPDATA%\Mozilla\sccache、macOS~/Library/Caches/Mozilla.sccache
  • 缓存大小:默认 10 GB,可通过SCCACHE_CACHE_SIZE调整,例如SCCACHE_CACHE_SIZE="1G"
  • 单服务器约束:本地存储同一时间只支持一个 sccache 服务器,多个并发服务器会互相竞争并导致随机的构建失败。
  • 只读缓存模式:本地缓存默认以读/写模式运行,设置环境变量SCCACHE_LOCAL_RW_MODE=READ_ONLY(或显式READ_WRITE)可禁止 sccache 向磁盘写入新的缓存条目。适合"只消费已有缓存、不新增缓存"的场景;需要注意的是,该模式仅在缓存中已有条目时才有意义,空缓存下使用它只会徒增开销而不产生任何收益。

小结:如何用好 sccache 的缓存键

理解哈希键的构成,是排查缓存命中率问题的起点:

  • Rust 项目:哈希键几乎覆盖了 rustc 的全部影响面(编译器本体、sysroot 共享库、所有源文件与依赖库、环境变量、cwd、版本号),因此任何环境或工具链变动都会自然导致缓存失效;要主动隔离不同调用之间的缓存,可通过CARGO_*相关机制或改造命令行参数实现。
  • C/C++ 项目:哈希键以预处理输出为主体,额外叠加编译器/汇编器摘要、语言与架构参数、环境变量等。多架构编译需显式设置SCCACHE_CACHE_MULTIARCH;启用预处理器缓存模式可以跳过最耗时的预处理环节,但在使用__DATE__/__TIME__/__TIMESTAMP__宏、-Wp,/-Xpreprocessor参数以及非本地存储时会被自动禁用,相关权衡(如file_stat_matchesignore_time_macrosskip_system_headers)可通过 docs/Configuration.md 描述的配置文件按需调整。
  • 环境变量是灵活的开关SCCACHE_DIRECT(预处理器缓存开关)、SCCACHE_CACHE_MULTIARCH(多架构缓存)、SCCACHE_LOCAL_RW_MODE(只读模式)均可在不改动构建脚本的前提下即时生效。

更详细的本地存储行为可继续阅读 docs/Local.md,完整配置项说明见 docs/Configuration.md,分布式缓存场景下的键值利用方式见 docs/Distributed.md。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:29:07

STM32 USB CDC虚拟串口配置

一、STM32内置USB虚拟串口简述 USB虚拟串口,简称VPC,Virtual Port Com 的简写。但更习惯于把虚拟串口叫作: CDC,因为它是利用 USB 的 CDC类 实现的一种通信接口。 1.1 为什么使用USB虚拟串口 在嵌入式开发中,串口(UAR…

作者头像 李华
网站建设 2026/9/16 15:27:50

卡密社区SUP系统总控与主站分销架构设计:签名鉴权与幂等实践

简介:一套完整的卡密社区SUP系统总控与主站分销源码,专为需要搭建卡密自助交易、分站分销业务的开发者或站长准备。系统涵盖总控端、主站后台和分站后台三套管理界面,可实现系统商模式的平台分配、卡密发行、分站开通与API对接,配…

作者头像 李华
网站建设 2026/9/16 15:27:50

网页设计大作业成品源代码:从模板修改到答辩演示的完整指南

简介:面向高校网页设计课程或大作业提交场景,这套多选一成品源码包提供了数套风格与难度各异的网页设计作品,涵盖网页制作基础课程作业、Web大作业、期末6页面个人主页等常见课题,并涉及Dreamweaver工具实践、视频嵌入、脚本交互等…

作者头像 李华
网站建设 2026/9/16 15:19:58

自指意义文明:认知科学与技术架构解析

1. 概念解析:什么是"自指意义文明"?"自指意义文明"这个复合概念由三个关键词构成:自指、意义、文明。拆解来看,"自指"指的是系统能够反身指向自身,形成递归式的认知结构;&qu…

作者头像 李华
网站建设 2026/9/16 15:18:30

自研轻量级全景监控系统:地图渲染与实时推送实战

运维和数据产品这个圈子里,有一种需求几乎每个团队都会遇到:设备分散在园区各个角落,业务系统各自为战,想看一眼全局状态,得同时打开七八个后台;真出了故障,排查链路基本靠电话和口头确认&#…

作者头像 李华