ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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/sccachesccache 是一款类似 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 --printsysroot获取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其顺序为一个缓存版本号CACHE_VERSION当前为b6见 src/compiler/rust.rs改动哈希输入时必须同步递增所有compiler_shlibs_digestssysroot 共享库摘要完整命令行其中--extern、-L、--check-cfg、--out-dir、--diagnostic-width等包含无关路径或不确定顺序的参数会被剔除--cfg被排序后追加指向 JSON 文件的--target参数内容单独参与哈希所有源文件的摘要通过rustc --emitdep-info输出的依赖清单获得见get_source_files_and_env_deps命令行列出的所有--extern依赖库的摘要命令行列出的所有静态库staticlib的摘要--target指向的 JSON 文件内容的摘要环境变量包括 rustc dep-info 输出中列出的所有# env-dep:变量以及所有CARGO_开头的环境变量但会剔除CARGO_MAKEFLAGS、CARGO_REGISTRIES_*token、CARGO_BUILD_JOBS、CARGO_ENCODED_RUSTFLAGS等不可缓存或已冗余的项编译时的当前工作目录cwdrustc 的版本字符串完整rustc -vV输出防止版本升级后复用旧产物。此外sccache 还会额外计算一个weak_toolchain_key由编译器可执行文件路径 其摘要构成用于规避编译器二进制是符号链接、摘要相同但真实路径不同带来的工具链定位错误见 src/compiler/c.rs 与 src/compiler/c.rs 中的注释说明。C/C 编译的哈希键对于 C/Csccache 的做法是先对源文件做预处理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_args与common_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_PATH、HIP_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_VARSsrc/compiler/preprocessor_cache.rs包括环境变量作用SCCACHE_C_CUSTOM_CACHE_BUSTER用户自定义的缓存破坏器设置为不同值即可阻止不同调用之间复用缓存CPATH头文件搜索路径影响包含解析结果C_INCLUDE_PATHC 头文件搜索路径CPLUS_INCLUDE_PATHC 头文件搜索路径OBJC_INCLUDE_PATHObjective-C 头文件搜索路径OBJCPLUS_INCLUDE_PATHObjective-C 头文件搜索路径预处理器缓存的数据结构预处理器缓存条目PreprocessorCacheEntrysrc/compiler/preprocessor_cache.rs本质上是一张结果键 → 包含文件列表的映射表磁盘格式为 1 字节版本号FORMAT_VERSION 1 bincode 序列化。每个IncludeEntry记录被包含文件的绝对路径、内容摘要、文件大小以及满足条件时记录的mtime/ctime。命中判定lookup_result_digest会从最新的结果开始倒序遍历逐一核对所有被包含文件是否仍然存在且未变化见result_matchessrc/compiler/preprocessor_cache.rs。为了防止条目无限膨胀源码还设置了两个上限单个条目最多保留MAX_PREPROCESSOR_CACHE_ENTRIES 100个结果全部被包含文件条目上限为 10000超限时直接清空重建。预处理器缓存模式何时被禁用预处理器缓存模式在以下任一情况下会被自动关闭编译的不是 C 或 C以及无需 C 预处理的语言见 src/compiler/c.rs配置项use_preprocessor_cache_mode为false使用的不是 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 无法感知的外部因素。预处理器缓存模式的配置项以下配置项定义于PreprocessorCacheModeConfigsrc/config.rs默认值如下配置项默认值说明use_preprocessor_cache_modetrue是否启用预处理器缓存模式。可通过环境变量SCCACHE_DIRECT对单次调用覆盖true/on/1或false/off/0覆盖逻辑见 src/config.rs 与 src/compiler/c.rsfile_stat_matchesfalse为false时仅通过哈希文件内容比较头文件是否变化为true时改用文件大小 ctime mtime快速比对配合下面的开关做更细粒度控制use_ctime_for_stattrue为true时使用 ctimeUNIX 上为状态变更时间Windows 上为创建时间判断文件是否变化在需要受控回拨修改时间backdate的场景可考虑关闭ignore_time_macrosfalse为true时忽略源码中的__DATE__、__TIME__、__TIMESTAMP__。能提升预处理器缓存模式的命中速度但可能产生过期结果skip_system_headersfalse为true时预处理器缓存只为系统头文件记录路径不缓存其内容减少缓存体积与比对开销hash_working_directorytrue为true时将当前工作目录加入缓存键以区分不同目录下的编译。对应实现见 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_SIZE1G。单服务器约束本地存储同一时间只支持一个 sccache 服务器多个并发服务器会互相竞争并导致随机的构建失败。只读缓存模式本地缓存默认以读/写模式运行设置环境变量SCCACHE_LOCAL_RW_MODEREAD_ONLY或显式READ_WRITE可禁止 sccache 向磁盘写入新的缓存条目。适合只消费已有缓存、不新增缓存的场景需要注意的是该模式仅在缓存中已有条目时才有意义空缓存下使用它只会徒增开销而不产生任何收益。小结如何用好 sccache 的缓存键理解哈希键的构成是排查缓存命中率问题的起点Rust 项目哈希键几乎覆盖了 rustc 的全部影响面编译器本体、sysroot 共享库、所有源文件与依赖库、环境变量、cwd、版本号因此任何环境或工具链变动都会自然导致缓存失效要主动隔离不同调用之间的缓存可通过CARGO_*相关机制或改造命令行参数实现。C/C 项目哈希键以预处理输出为主体额外叠加编译器/汇编器摘要、语言与架构参数、环境变量等。多架构编译需显式设置SCCACHE_CACHE_MULTIARCH启用预处理器缓存模式可以跳过最耗时的预处理环节但在使用__DATE__/__TIME__/__TIMESTAMP__宏、-Wp,/-Xpreprocessor参数以及非本地存储时会被自动禁用相关权衡如file_stat_matches、ignore_time_macros、skip_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),仅供参考
返回列表