← Back to news

Faster Than Ninja

build2.org|54 points|19 comments|by elasticdog|Aug 5, 2026

Surpassing the "Speed of Light": Faster Than Ninja

Posted on 5 Aug 2026 by Boris Kolpackov

A few months back, I came across a piece discussing a utility designed to sniff out bottlenecks in sluggish build systems. While the entire article was insightful, one particular passage caught my eye:

Ninja is not a 100% fair comparison to other tools, because it benefits from some "baked in" build logic by the tool that created the ninja file, but I think it's a reasonable "speed of light" performance benchmark for build systems.

To put this in plain English: nobody actually writes Ninja files by hand.

Instead, a generator (like CMake) is used to produce them. This means certain logic—which logically belongs in the build phase—is actually executed during the generation phase. Furthermore, Ninja is designed to be aggressively minimalist, offering only the most basic functionality, particularly regarding how it tracks changes.

This raised a question for me: could a modern, native build system (one that skips the generation step entirely) actually approach or even exceed this "speed of light"?

The Experimental Setup

Finding a substantial project for an "apples-to-apples" comparison is tricky. While projects like Qt or Boost support multiple systems, packaging complex projects for build2 often requires restructuring a "ball of intra-dependencies" into a more organized format.

Comparing the upstream qtbase module to build2 packages would be unfair because the intermediate artifacts (like utility libraries) differ. Therefore, I needed a simpler project where the object files and binaries remained identical across both systems.

I chose Xerces-C++, a C++ XML parser and serializer. It is sufficiently complex—featuring XML Schema validation and consisting of 299 C++ translation units linked into a single shared library.

Workflow Comparison

The Baseline Results

I performed a full, from-scratch build. On my machine, the results were as follows:

Build SystemMean Time (μ±σ\mu \pm \sigma)User TimeSystem TimeRange (Min...Max)
Ninja3.429s±0.029s3.429\text{s} \pm 0.029\text{s}48.536s5.033s3.383s ... 3.464s
build23.808s±0.046s3.808\text{s} \pm 0.046\text{s}58.037s8.012s3.746s ... 3.886s

At first glance, build2 is roughly 11% slower. However, we must address the "elephant in the room": CMake took 15.6s to generate the Ninja files.

If Xerces-C++ were a dependency in your project, the total wait time for Ninja would be 19s\approx 19\text{s}, whereas build2 remains at 3.8s3.8\text{s}.

Closing the Gap

To see if build2 could truly match Ninja, I decided to disable some of its high-level features to mirror Ninja's minimalism.

  • Disable Advanced Change Tracking
  • Disable File Cache Compression

1. Change Tracking

Ninja is simple: it checks the file's modification timestamp. If it has changed, it recompiles.

build2 is more sophisticated. It tokenizes the partially-preprocessed source and computes a checksum. This allows it to ignore whitespace-only changes, which is a massive productivity boost during development. However, tokenizing 299 units adds overhead to a clean build.

By marking the project as read-only (the default for external dependencies), build2 reverts to simple timestamp checking.

New Result: 3.433s±0.055s3.433\text{s} \pm 0.055\text{s} (Now equal to Ninja).

2. Cache Optimization

By disabling compression in the file cache, we trade disk space for raw execution speed.

New Result: 3.355s±0.067s3.355\text{s} \pm 0.067\text{s}

Conclusion: build2 is now 2.2% faster than Ninja.

The Nuance of "Work Done"

While a 2.2% difference seems marginal, it is significant because build2 is still doing work that Ninja simply ignores (or delegates to CMake).

Examples of build2's additional responsibilities:

  • Header Generation: build2 generates XercesVersion.hpp from XercesVersion.hpp.in during the build and tracks it. Ninja expects CMake to handle this.
  • Compiler Introspection: build2 actively extracts the following data from the compiler:
    • Compiler ID (e.g., GCC, Clang) and version.
    • Target platform details.
    • System header and library search paths.
    • C/C++ standard configurations.

To test the impact of the header generation, I briefly modified the build file to treat XercesVersion.hpp as a static file.

Benchmark Graph Placeholder

In summary, by stripping away some of its "intelligence," build2 can not only match the "speed of light" but slightly exceed it, all while maintaining a native workflow without a separate generation phase.