Bun 1.4 shipped on 20 August as the first stable release built on Bun's new Rust-based core, replacing the Zig runtime layer that shipped everything since 1.0. The release notes claim faster startup, less idle CPU, and lower memory. I installed the last pre-rewrite build and the new one side by side and measured all three, plus what happens when you point a node symlink at the new binary, which is how a lot of people actually run Bun in production.
I put Bun 1.3.14 in ~/.bun-old and 1.4.2 in ~/.bun-new using the official install script with a pinned version tag, so both binaries sit on the same machine and I can call either one directly.
My first pass measured bun --version twenty times per binary. The difference was almost nothing: 2.24ms median for 1.3.14, 2.00ms for 1.4.2. That command exits before it initialises the JS engine, so it wasn't measuring what the release notes were talking about. Once I switched to actually running a script, the gap showed up.
Thirty runs of bun run hello.js each, timed with time.perfcounter() around a subprocess.run call:
| | median | min | max | |---|---|---|---| | 1.3.14 | 10.54ms | 9.32ms | 18.00ms | | 1.4.2 | 4.22ms | 3.90ms | 5.25ms |
That's a 60% drop in median startup time, better than the "50% faster on Linux" the release notes claim. The old binary also had a much longer tail (up to 18ms), while the new one stayed inside a 1.4ms band across all 30 runs.
I ran a two-line Bun.serve echo server on each version, hit it once to warm it up, then read VmRSS from /proc//status after a further 60 seconds of doing nothing. Three runs each:
| | RSS after warmup | RSS after 60s idle | |---|---|---| | 1.3.14 (run 1) | 34,876 KB | 34,960 KB | | 1.3.14 (run 2) | 34,924 KB | 34,928 KB | | 1.3.14 (run 3) | 34,872 KB | 34,936 KB | | 1.4.2 (run 1) | 18,864 KB | 18,920 KB | | 1.4.2 (run 2) | 19,068 KB | 19,136 KB | | 1.4.2 (run 3) | 19,056 KB | 19,144 KB |
Averaging the idle figures: 34.9MB down to 19.0MB, a 46% reduction. The release notes say "up to 35%", so on this workload it beat its own number, though a bare echo server is close to the best case for a claim like this — there's no application memory to dwarf the runtime's own footprint.
The release notes claim 5x less idle CPU. This is the one I'd flag as not backed by what I measured.
I sampled utime + stime from /proc//stat before and after a 60-second idle window, three times per version (100 clock ticks per second, so each tick is 10ms of CPU time):
| run | 1.3.14 ticks/60s | 1.4.2 ticks/60s | |---|---|---| | 1 | 11 | 6 | | 2 | 11 | 4 | | 3 | 10 | 3 | | mean | 10.67 | 4.33 |
That's roughly 0.178% average CPU for 1.3.14 against 0.072% for 1.4.2 — a real improvement, and in the right direction, but a 2.5x reduction, not 5x. Both numbers are small enough that a single bad sample would swing the ratio a lot, which is exactly why I went from a 5-second window (0 to 2 ticks total, useless) to 60 seconds before trusting any of it. It's possible Bun's 5x figure comes from a different idle workload — something with an active file watcher or timer loop rather than a bare Bun.serve with no traffic — but on the simplest possible idle server, I got 2.5x.
The release notes don't claim a throughput number, so I measured one anyway, since idle numbers only tell you about servers doing nothing. I ran autocannon -c 50 -d 8 against the same echo server, three runs per version:
| run | 1.3.14 avg req/s | 1.4.2 avg req/s | |---|---|---| | 1 | 29,456 | 62,310 | | 2 | 29,578 | 61,998 | | 3 | 28,042 | 62,922 | | mean | 29,025 | 62,410 |
That's 2.15x more requests per second at 50 concurrent connections, and p50 latency dropped from 1ms to sub-millisecond in autocannon's output. This one held up better under concurrency than at idle, which is the opposite of what I expected going in — I assumed a rewrite aimed at idle efficiency would show its biggest gains with nothing happening, not under load.
Bun positions itself as a drop-in replacement for Node, and one common way people use that is symlinking a node binary to bun so existing tooling picks it up without changes. I tested exactly that setup.
That's 1.3.14, invoked through a symlink literally named node. It loads .env as expected. Now the same setup against 1.4.2:
