Orbit C++ → Rust · Post 13

Fourteen Times Faster

"Ease of build" was one of the four goals set at the start, and the vaguest. Here is what it turned into: the C++ service takes two and a half minutes to build from cold, the Rust one takes eleven seconds.

148.5 sC++ service, cold
10.7 sRust service, cold
13.9×Faster
4,506Bazel actions
116Rust crates
5.0 sRust incremental

Build time is the tax you pay on every idea. It sets how many things you try before lunch, whether you check a hunch or talk yourself out of it, and how long a bisect takes. It is the goal that sounds softest and compounds hardest, so it is worth measuring rather than asserting.

The measurement

C++ OrbitServiceRust orbit-service
cold build148.5 s10.7 s
work units4,506 Bazel actions116 crates
incremental (one file)—5.0 s
build toolBazelcargo

Both on the same 24-core / 32-thread desktop. Both genuinely compile from scratch: the C++ ran in a private Bazel output base with the disk cache disabled, the Rust in a fresh CARGO_TARGET_DIR. Neither timing includes downloading dependencies — external repositories and the crates.io registry were already present, because the interesting question is compile cost, not network weather.

Why the gap is this wide

It is tempting to say "Rust is faster to build than C++", which is not generally true and is not what happened here. Three specific things did.

The dependency tree shrank enormously. The C++ service pulls gRPC, protobuf, abseil, capstone, libunwindstack, and their transitive closure — 4,506 actions worth. The Rust service reaches the same capabilities through 116 small crates, and the biggest single win was earlier in the port: deleting six LLVM libraries in favour of object, gimli, pdb and msvc-demangler. That change alone took ObjectUtils's cold build from 158.7 s to 104.6 s, a third off one target, before any of the rest existed.

There is no header model to re-expand. C++ recompiles the same headers into every translation unit that includes them; the work is quadratic in a way that tooling can mitigate but not remove. Rust compiles each crate once and hands the next crate metadata. A 116-crate graph parallelises cleanly — the cold build ran at 1,052% CPU, so roughly ten cores stayed busy.

Nothing is generated. The pod wire format replaced protobuf on the capture path, so there is no schema compiler in the build at all: no protoc, no generated sources, no plugin. The format is a tag byte and fixed fields, and the thing that reads it is ordinary Rust.

What the number does not say

Why eleven seconds is a different activity

Two and a half minutes is long enough to switch context. You start the build, read something, come back, and reload what you were doing. Eleven seconds is not — you watch it finish. Five seconds, the incremental case, is inside the window where a build feels like part of the edit rather than an interruption.

That difference shows up in behaviour, not just in a table. Almost every measurement in this log — the differentials over millions of instructions, the sabotage checks that deliberately break the Rust to prove the harness bites — exists because rebuilding to test a hypothesis was cheap. The habit of checking rather than assuming is affordable at eleven seconds and expensive at two and a half minutes. That is the real return on "ease of build", and it is not visible in the number itself.