Skip to content
Latchkey
Published June 2026 by Daniel Zoghalchali

The State of C/C++ CI 2026

In C and C++ the compile is the workload, which is why a persistent compile cache and a self-healing runner matter more here than in any other ecosystem: long cold builds across a compiler-by-platform matrix multiply everything.

79%
of C/C++ CI wall-clock spent compiling and linking, not testing
Latchkey analysis (modeled)
8.3x
cold full build vs warm build at a high ccache hit rate
Latchkey analysis (modeled)
5.0x
pipeline minutes for a full compiler-by-platform matrix vs one config
Latchkey analysis (modeled)

Executive summary

C and C++ have the most punishing CI of any mainstream ecosystem, and the reason is simple: the compile itself is the workload. In most languages the tests dominate the clock and the build is a quick preamble; in C and C++ that ratio is inverted. Template-heavy translation units, deep header expansion, and the link step dominate the clock, and a clean build of a real codebase runs for many minutes before a single test executes. The build is not the cost of getting to the workload; the build is the workload.

On a cold runner this expensive work happens from scratch on every push. The compiler re-parses the same headers, re-instantiates the same templates, and re-emits the same object files, not because anything changed but because the cache that held last build's outputs was discarded with the previous runner. The fundamental waste of C/C++ CI is not that compilation is hard, it is that the same compilation is repeated needlessly, and that waste is paid in the most expensive minutes in any pipeline.

With CI adoption at 76% among professional developers, the C/C++ question in 2026 is not whether to run CI but how to stop paying for full cold builds repeatedly across a matrix. This report quantifies the build-time tax, what ccache and sccache actually save when the cache survives, and why the compiler-by-platform matrix is the multiplier that turns a long build into an expensive one. The compile cache is the central lever, and the matrix is the thing that makes getting it right matter most.

The cruel part is that almost none of this is new work. The same objects are recompiled on every run because the cache was thrown away with the runner, and the matrix multiplies that repeated work by every cell in the grid. A persistent managed compile cache shared across the fleet is what turns the matrix from a multiplier on a cold build into a multiplier on a mostly-warm one, which is the difference between a matrix that is merely thorough and a matrix that is ruinously slow.

Long builds also invite the most expensive failures in CI. An out-of-memory kill during heavy template instantiation, a linker timeout, or a toolchain download failure fails the build late, after most of the compile cost has already been paid, and it passes on a clean retry. Re-running a forty-minute build because of a transient hiccup is the single most expensive flake in any ecosystem, which is why self-healing runners, the same lever that helps elsewhere, pay back fastest of all in C and C++.

Where a C/C++ CI minute goes
Compilation 58%
Linking 21%
Test execution 13%
Configure, fetch, setup 8%

Modeled split of billed minutes for a native build pipeline. · Source: Latchkey analysis (modeled)

Build time by ccache hit rate
Cold, 0% hit41 minGitHub cache, ~60% hit19 minWarm, ~85% hit9 minManaged cache, ~95% h…5 min

Full build wall-clock as cache effectiveness rises. · Source: Latchkey analysis (modeled)

Email me the report

The full report is right here on this page, free. Want the link in your inbox to read later or share, plus new Latchkey reports as they drop? Drop your email and we will send it over.

Sent! Check your inbox for the report link.

No spam. Unsubscribe anytime.

In C/C++ the build is the workload

Unlike most ecosystems where tests dominate, compilation and linking are the overwhelming cost of a C/C++ pipeline. The modeled split of where a native CI minute goes, shown below, puts compilation and linking together at roughly four fifths of the clock, with test execution a minority slice. This is the structural fact that makes C/C++ CI different from everything else: optimizing the tests barely moves the total, because the tests were never where the time went.

The reason is in the language. Template instantiation, header inclusion, and the preprocessor mean each translation unit can pull in and re-expand enormous amounts of code, and the compiler does real, heavy work on every one. A header-heavy codebase can spend the majority of its compile time on code that is included rather than written locally, and the link step then has to resolve and combine all of it. A cold full build of a real codebase runs for many minutes before testing even starts.

This is why every meaningful optimization in C/C++ CI is really a build-cache optimization. In an ecosystem where the build is a quick preamble, you tune the tests; in C/C++, where the build is the workload, you tune the build, and tuning the build means not doing it from scratch every time. The rest of this report follows from that single fact: the compile cache is the lever, and almost everything else is about keeping it warm or stopping the matrix from multiplying the cold case.

ccache and sccache only pay off when the cache survives

A high ccache or sccache hit rate collapses build time dramatically. These compiler caches work by hashing the preprocessed source and compiler flags and serving the previously compiled object when the hash matches, so an unchanged translation unit is restored in milliseconds instead of recompiled in seconds. At a high hit rate, the build skips the great majority of its compilation and finishes in a fraction of the cold time.

The catch is that on ephemeral hosted runners the cache is discarded with the instance, so the hit rate starts near zero every run. The cache that ccache so carefully built up during the last job is gone, and the new job begins with an empty cache and a 0% hit rate, recompiling everything as if it had never been compiled before. The tool is doing its job perfectly; the runner is throwing away the thing the tool produces.

Persisting the compile cache across the fleet is what keeps the hit rate high, turning the dominant cost of the pipeline into a fraction of a cold build. The chart of build time by ccache hit rate, shown below, traces the gradient: a cold 0% build is the worst case, a shared cache at a high hit rate is several times faster, and a managed cache near full effectiveness is the floor. The hit rate is not a property of ccache; it is a property of whether the cache survives, which is a property of the runner.

  • ccache and sccache restore an unchanged object in milliseconds instead of recompiling it in seconds.
  • On ephemeral hosted runners the cache dies with the instance, so every run starts near a 0% hit rate.
  • A persistent fleet-shared cache is what keeps the hit rate high and the build fast; the hit rate is a runner property, not a tool property.

The compiler-by-platform matrix is a brutal multiplier

Building across gcc, clang, and MSVC on Linux, Windows, and macOS multiplies an already long build by every cell in the grid. A matrix that validates three compilers across three platforms can be many times the cost of a single configuration, and because each cell is a full long build, the matrix takes an expensive thing and runs it again and again. The chart of pipeline minutes by matrix width, shown below, climbs steeply as each compiler and platform is added.

On hosted runners the platform multiplier stacks on top of the cell count. A macOS minute costs 10x a Linux minute and Windows 2x, so the expensive-platform cells of the matrix are not just additional long builds, they are additional long builds billed at a premium. A few matrix cells on macOS and Windows can dominate the entire bill even though they are a minority of the grid, which makes them the first line item to audit.

The matrix is rarely wrong to exist, because cross-platform correctness genuinely requires building on each target, but it is frequently wider and colder than it needs to be. The fix is two-fold: keep the cache warm so each cell is a mostly-warm build rather than a full cold one, and reserve the expensive platforms for the work that truly requires them rather than running the whole grid cold on every push. A warm cache turns the matrix from a multiplier on a cold build into a multiplier on a fast one.

Pipeline minutes by compiler-platform matrix width
gcc / Linux only41 min+ clang / Linux82 min+ MSVC / Windows148 min+ clang / macOS205 min

Total billed minutes as the build matrix grows. · Source: Latchkey analysis (modeled)

Long builds invite the most expensive flakes

OOM kills during heavy template instantiation, linker timeouts, and toolchain download failures share a cruel property: they fail the build late, after most of the compile cost has already been paid. A flaky test in a fast ecosystem wastes a few minutes; a transient failure forty minutes into a C/C++ build wastes forty minutes of the most expensive compute in the pipeline, and then the retry pays it all again.

These failures are overwhelmingly mechanical and pass on a clean retry. A template-heavy translation unit that exhausted memory on an undersized runner compiles fine with more headroom or on a second attempt; a linker that timed out under load links cleanly when retried; a toolchain download that hit a registry blip succeeds on the next pull. The build was never broken. The environment hiccuped late in a long, expensive process, which is the worst possible time for a hiccup.

Re-running a forty-minute build because of a transient failure is the most expensive flake in any ecosystem, which is exactly why self-healing runners pay back fastest here. When a step fails on a known-transient signal, a self-healing runner retries it automatically on a fresh environment before a human ever sees a red check, so the failure never reaches the pull request and the cost is a recovery rather than a full repaid build plus an engineer's context switch.

  • OOM kills, linker timeouts, and toolchain download failures fail builds late, after most of the compile cost is already paid.
  • These failures are overwhelmingly mechanical and pass on a clean retry; the build was never broken.
  • Re-running a forty-minute build for a transient hiccup is the single most expensive flake in any ecosystem.
  • Self-healing retries the transient failure automatically so it never reaches the pull request or repays the whole build.

Incremental builds depend on the cache, not just the build system

CMake, Ninja, and the like are built to do incremental work: rebuild only the translation units whose inputs changed and relink, rather than rebuilding the world. On a developer's local machine this is what makes iteration tolerable, because the build directory persists and only the changed objects recompile. The build system is perfectly capable of doing proportional work.

On a cold CI runner that incremental capability is neutralized, because there is no previous build directory to be incremental against. Every CI build is effectively a clean build from an empty tree, so the build system's careful dependency tracking has nothing to compare to and recompiles everything. The incrementality that makes local development fast is exactly the thing the cold runner discards, which is why CI builds are so much slower than the equivalent local rebuild.

A persistent compile cache restores the incremental behavior in CI by a different mechanism: instead of an incremental build directory, ccache or sccache supplies the previously compiled objects keyed by content hash, so unchanged translation units are served from cache even on a clean tree. The build system and the compile cache are complementary, the build system decides what to do and the cache supplies the results of work already done, and together they make a CI build do work proportional to the change rather than proportional to the codebase.

The build-time tax compounds with codebase size

C and C++ codebases tend to grow in exactly the dimensions that make compilation expensive: more headers, deeper template usage, more translation units, and more inter-module dependencies that the linker must resolve. A young project compiles quickly because there is little to compile; a mature one compiles slowly because there is a great deal to compile and the dependency graph has thickened, so changes ripple into rebuilds of more units.

This means the cold-build tax does not stay constant, it compounds. A team comfortable with a five-minute cold build at the start of a project can find that same build taking many times longer two years later, and because the matrix multiplies the cold case, the total CI minutes grow faster still. Without a warm cache, the build cost grows with the size of the codebase rather than with the size of any individual change, which is the curve that turns C/C++ CI from slow into untenable.

Breaking that curve is the same move as everything else in this report: make the build do work proportional to the change by keeping the compile cache warm, so a one-file edit recompiles one file rather than the world. A persistent fleet-shared cache moves the cost driver from size-of-codebase back to size-of-change, which is what keeps a large, mature C/C++ project's CI fast even as the code that has to be correct keeps growing.

Managed runners are the clearest win in C/C++ CI

No ecosystem benefits more from a right-sized, cache-persistent, multi-platform managed layer than C/C++. The three things that define C/C++ CI pain, a dominant compile cost, an expensive cross-platform matrix, and late transient failures on long builds, are precisely the three things a managed runner with a persistent cache and self-healing addresses, which is why the return here is the clearest of any ecosystem Latchkey models.

Managed runners capture roughly 70% of the hosted cost with zero ops, and the saving compounds along multiple axes at once. Persisting the compile cache keeps the ccache hit rate high so each build, including each matrix cell, is mostly warm rather than fully cold. Providing more cores cheaply lets the build parallelize, which a compile workload exploits almost perfectly. And self-healing absorbs the OOM and toolchain flakes that long builds invite, so a transient failure is a quiet retry rather than a fully repaid forty-minute build.

Put together, these cut both the per-minute rate and the raw build minutes the matrix demands, and because they attack the cost from several directions simultaneously, they multiply rather than add. The chart of per-minute cost by platform, shown below, shows the rate gap, but in C/C++ the rate is only part of the story: the warm cache cuts the minutes, the extra cores cut the wall-clock, and the self-healing cuts the wasted re-runs, and a build-dominated, matrix-wide pipeline feels all three.

Hosted runner cost per minute by platform
Linux 2-core$0.008Windows 2-core$0.016macOS$0.08Managed (Latchkey)$0.0025

Published GitHub-hosted rates vs a managed alternative. · Source: GitHub Actions pricing + Latchkey rates

Recommendations

Persist the compile cache across the fleet

ccache and sccache only pay off when the cache survives between jobs, and on ephemeral hosted runners it does not. Run native builds where the compile cache persists and is shared across the fleet, so the hit rate stays high and each build, including each matrix cell, is mostly warm rather than a full cold recompile. This is the single highest-return change in C/C++ CI.

Audit the expensive-platform matrix cells first

A few macOS and Windows cells can dominate the bill because their per-minute rates carry a large platform multiplier on top of an already long build. Reserve the expensive platforms for the work that genuinely requires them, push OS-agnostic compilation and unit tests onto Linux, and keep the cache warm so the cells you do keep are fast. The expensive cells are the first line item to cut.

Auto-heal the late transient failures long builds invite

OOM kills, linker timeouts, and toolchain download failures fail long builds late, after most of the compile cost is paid, and they pass on a clean retry. Retry these mechanical failures automatically on a fresh environment so a transient hiccup forty minutes into a build is a quiet recovery rather than a fully repaid build and an engineer's interrupted afternoon.

Size runners to the compile, and give the build cores

Compilation parallelizes almost perfectly across translation units, so a build-dominated pipeline turns extra cores directly into shorter wall-clock, and adequate memory headroom prevents the OOM kills that template-heavy units provoke. Right-size the runner to the build rather than defaulting to the smallest tier, and prefer infrastructure where more cores are cheap enough to throw at the compile.

Move the build-dominated pipeline onto managed infrastructure

C/C++ CI concentrates its cost in repeated compilation across an expensive matrix with late transient failures, which is exactly the combination a managed runner with a persistent cache and self-healing addresses. Moving the build onto managed infrastructure cuts the per-minute rate, keeps the cache warm, supplies cheap cores, and absorbs the flakes, and because these attack the cost from several directions they multiply.

Outlook

Expect the build-time tax in C and C++ to keep rising in absolute terms even as tooling improves, because codebases grow in exactly the dimensions that make compilation expensive: more headers, deeper templates, more translation units, thicker dependency graphs. The teams that keep a warm, fleet-shared compile cache will hold their CI times roughly flat as the code grows, while the teams that rebuild cold will watch their build minutes grow with the codebase and then multiply across the matrix.

The cross-platform matrix is unlikely to shrink, because cross-platform correctness genuinely requires building on each target, and if anything the pressure is toward more targets as Arm and additional toolchains become first-class. That makes the cost of getting the cache wrong larger over time, not smaller: a wider matrix multiplies whatever the per-cell build costs, so the warm-cache discipline that keeps each cell fast becomes more valuable as the grid grows.

The durable takeaway is that C/C++ CI rewards the same mechanical hygiene as every other ecosystem, only more, because the build is the workload and the matrix multiplies it. A persistent compile cache, right-sized runners with cheap cores, and self-healing for the late transient failures are not exotic; they are the standard levers applied where they matter most. The teams that wire them in will treat a build-dominated, matrix-wide pipeline as fast and routine, while their peers keep repaying full cold builds across every cell on every push.

Methodology

This report combines public C/C++ ecosystem signals (compiler and build-system behavior, published runner pricing, ccache and sccache hit-rate characteristics) with Latchkey runner analysis of native build pipelines. Modeled figures reflect a representative mid-size C++ codebase built with CMake across a compiler and platform matrix. Figures labeled "modeled" are illustrative estimates derived from public pricing and typical pipeline shapes, not a primary survey; figures attributed to a named source reflect that source. Pricing reflects published rates at time of writing and should be verified against current provider pricing.

Sources

More Latchkey reports

See what you would save with Latchkey managed runners and self-healing. Start free → 30-day trial · No credit card