Faster Than Ninja
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 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.qtbase module to build2 packages would be unfair
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 System | Mean Time () | User Time | System Time | Range (Min...Max) |
|---|---|---|---|---|
| Ninja | 48.536s | 5.033s | 3.383s ... 3.464s | |
| build2 | 58.037s | 8.012s | 3.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 , whereas build2 remains at .
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: (Now equal to Ninja).
2. Cache Optimization
By disabling compression in the file cache, we trade disk space for raw execution speed.
New Result:
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:
build2generatesXercesVersion.hppfromXercesVersion.hpp.induring the build and tracks it. Ninja expects CMake to handle this. - Compiler Introspection:
build2actively 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.
- Compiler ID (e.g.,
To test the impact of the header generation, I briefly modified the build file to treat XercesVersion.hpp as a static file.
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.