I kept repeating 'Rust is faster.' So I sat down and measured it.

frontendrustbenchmarkperformancev8

I wrote a whole post about how the JavaScript build toolchain got rewritten in Rust. SWC replaced Babel, Turbopack replaced Webpack, and I mapped the whole thing. But there was one line in it I just repeated without earning: Rust is faster.

Faster how? Faster by how much? I didn't know. I had passed the sentence along the way everyone does, like a coin worn smooth from being handed over so many times.

So I sat down and built a benchmark. Not to prove anything, just to find out. I wrote down what I expected first, then ran it, so I couldn't quietly move the goalposts afterward.

It isn't one number

The first thing that fell apart was the sentence itself. "Rust is faster" wants to be a single number. It isn't. Depending on what the code is actually doing, the same claim came back as anything from a dead tie to 37 times.

Look at the left end. Summing ten million floats, the two tied. Node 9.556ms, Rust 9.553ms. Not close, the same. A running sum is a chain, each addition waiting on the one before it, so both sides sat at the latency of a single floating-point add. No machine code, however good, gets to skip that wait.

And look further left still: for churning through short-lived objects, Node's median was actually faster. Its generational garbage collector is built for exactly that, allocating is barely a pointer bump. That was the one result that argued against my own title, and it was the one I trusted most, because I didn't want it.

The one that unsettled me

Then this. The exact same loop, one thing changed: how long the array is. And the winner flipped.

The loop does one small thing per number: if it clears a threshold, add it to a running total; otherwise skip it.

for each number in the array:
    y = number * 2
    if y > 1:  total = total + y    # add it
    else:      total = total        # skip, unchanged

The data is random, so that if is true about half the time. And to keep the sizes comparable, I held the total work constant: about 200 million element-visits at every length, so a small array just gets walked many more times than a big one. The chart is cost per element, not total time. A longer array isn't doing more work; each element is simply costing more.

So why would the same element cost more just because the array is longer? The if.

Node keeps it as a real branch — two roads, add or skip. A modern CPU can't wait to find out which, so it guesses and races ahead. Guess right, no time lost. Guess wrong, it throws away the work it already ran and redoes it, about thirteen cycles gone. The guess is based on how this branch behaved recently.

Rust builds it with no branch at all. Its compiler, through LLVM, always computes the added total, keeps the unchanged one too, and picks the right one with a single "conditional select" instruction, fcsel on this chip. No jump, so nothing to guess:

fadd   d2, total, y      ; compute total + y no matter what
fcmp   y, threshold      ; compare
fcsel  total, d2, total  ; take total+y if it cleared the bar, else keep total

That is the whole flip:

  • Small array: the short sequence of add/skip gets walked over and over, so the CPU learns it and guesses right nearly every time. Cheap. Random doesn't stop this; a short fixed sequence is learnable even when it looks random.
  • Large array: the sequence is too long for the CPU to learn, so it guesses wrong about half the time, and every miss costs those thirteen cycles. Expensive.

Rust never guesses, so its cost is flat at every size. Same code, only the length changed, and the winner reversed. "Rust is faster on unpredictable branches" had quietly assumed the array was long enough to make the branch unlearnable in the first place.

So where do the 20-70x claims come from?

This was the real question under all of it. SWC says it's 20x faster than Babel on one thread, 70x on four cores. If a single-threaded language gap is only around three times, where does the rest come from?

I brought in a third tool: esbuild. It's written in Go, not Rust, but it too is compiled to a native binary and designed from scratch.

esbuild is just as far ahead of Babel, right next to SWC. All that shows is that the big multiple is reachable in Go too, so it doesn't need Rust. The real answer to how much the language accounts for isn't here; it's back in the same-algorithm test: an identical tree walk, JS against native, only about 3x. That is the small slice. The 8-to-51x on top of it is architecture, Babel's visitor pattern, plugin pipeline, and constant node rebuilding, and that has nothing to do with which language.

And the "four cores" half of the 70x isn't Rust either. On the same work, eight threads made Node 5.8x faster and Rust 6.3x, both sides pick up the win. The point is that Node is JavaScript, the same language as Babel, and it still uses every core through its built-in worker threads. So nothing about JavaScript blocks a tool from doing this. Babel just never did.

So, adding it up

The 20 to 70x isn't one number, and almost none of it is the word "Rust". It breaks into three pieces:

  • About 3x: leaving JavaScript for a native binary. A build tool runs once and exits, so a JIT never warms up in time.
  • The biggest piece, architecture: Babel walks the tree once per plugin and keeps rebuilding nodes; SWC was designed from scratch. It grows with the work: switch the target from ES2019 to ES5 and Babel's own time balloons 27x, while SWC's grows only 4.6x.
  • ×4, cores: Babel never used them. Node's worker threads pick up the same speedup.

A Go tool, esbuild, keeps pace with the Rust one on every part. Native compilation, a redesigned architecture, and parallelism, three factors and not one of them is Rust. Rust is a fine language to build these in. It just isn't the reason they got fast.

The toolchain story that started all this is here.