ENH: Optimize GitHub Actions ccache strategy for faster C++ builds#5954
Conversation
Key changes from upstream: 1. Remove CCACHE_NODIRECT=1: Enables direct mode so ccache skips the preprocessor for cache lookups, significantly faster on hits. 2. Add CCACHE_SLOPPINESS=pch_defines,time_macros: Avoids cache misses caused by __DATE__/__TIME__ macros that change every build. Standard CI-safe setting. 3. CCACHE_MAXSIZE 2.4G -> 5G: ITK has ~4000 translation units; a larger cache retains more objects across incremental builds. 4. SHA-based cache key with restore-keys fallback: - key: ccache-v4-<os>-<config>-<sha> (unique per commit) - restore-keys: ccache-v4-<os>-<config>- (most recent match) Each build creates a new cache entry; restore-keys always finds the most recent cache for the same OS and configuration. Replaces the old static key that could never be updated once written. 5. Save on !cancelled() instead of main-only: PR builds now persist their cache for subsequent pushes, but cancelled runs do not save potentially incomplete caches. 6. Use runner.os (Linux/macOS/Windows) instead of matrix.os (ubuntu-24.04-arm/macos-15) for cache key consistency across runner image updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
/azp run ITK.Windows |
Post-merge CI timing analysis — 45-day measurement@dzenanz @thewtex — sharing a quantitative analysis of the build time impact from this PR, measured over 700+ successful CI runs across the full 45-day window (2026-02-17 → 2026-04-03). Three-period breakdown
Period B was counterproductive — Hypothesis: CONFIRMED
Open question for @dzenanz and @thewtexThe Full analysis with raw data tables: |
|
@hjmjohnson awesome!! 🚀 |
Summary
Use best practices now that we have a quota of 50GB for cache storage (Big enough so that we do not constantly evict PR's based on running over the 10GB limit).
Key knowlege: Caches are not mutable. Once written, they can not be updated. That is why the git sha is recommended with fallback patterns to gain the latest commit with the same prefix (to keep the cache warm).
CCACHE_NODIRECT=1so ccache skips the preprocessor for cache lookups (significantly faster on hits)CCACHE_SLOPPINESS=pch_defines,time_macros: Avoid cache misses from__DATE__/__TIME__macros that change every build (standard CI-safe setting)CCACHE_MAXSIZEfrom 2.4G to 5G: ITK has ~4000 translation units; a larger cache retains more objects across incremental buildsrestore-keysfallback: Each commit produces a unique cache entry (ccache-v4-<os>-<config>-<sha>), whilerestore-keysprefix match always restores the most recent cache for the same OS and configuration. Replaces the old static key that was immutable once written!cancelled()instead of main-only: PR builds now persist their cache (success or failure), but cancelled runs skip saving potentially incomplete cachesrunner.osinstead ofmatrix.osin cache keys for consistency across runner image updatesGitHub Actions Cache Key Notes for ccache
What does
if: ${{ !cancelled() }}do?Controls when a step runs based on the workflow's status:
if: always()if: ${{ !cancelled() }}For the ccache save step,
!cancelled()saves the cache on both success and failure(compilation progress is still valuable), but skips saving on cancellation to avoid
persisting an inconsistent mid-write cache.
What is
${{ github.sha }}and does it work with merged branches?${{ github.sha }}is the full 40-character Git commit SHA that triggered the workflow.Appending it to the cache key (e.g.,
ccache-v4-Linux-name-<sha>) makes each commitproduce a unique, immutable cache entry. The
restore-keysprefix fallback ensureseach build restores the most recent cache for the same OS and configuration.
Branch scope restriction
GitHub Actions caches are scoped by branch:
mainmainonlymain(base branch)After a PR is merged, the main branch build cannot access caches created on the
PR branch. It falls back to the most recent cache saved by a prior main build. This
means main does not benefit from incremental cache improvements made during PR CI,
but in practice, this is acceptable because the main builds frequently enough to keep its
own cache warm.
🤖 Generated with Claude Code