JavaScript engines do a lot of invisible work to make your code fast. One of the more subtle costs is boxing — the process of wrapping a plain numeric value in a heap-allocated object so the runtime can treat it uniformly with other values. Most of the time this happens fast enough that you never notice. But push it into a hot loop and it becomes death by a thousand allocations.
The V8 team recently published a detailed writeup on exactly this problem, and the fix they landed is satisfying in that particular way where a small conceptual shift produces an outsized result.
The Problem: A seed That Wouldn’t Sit Still
The trigger was the async-fs JetStream2 benchmark, which implements its own deterministic Math.random:
let seed;
Math.random = (function() {
return function () {
seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
// ... four more lines like this
return (seed & 0xfffffff) / 0x10000000;
};
})();
This is called constantly throughout the benchmark. The variable seed lives in a ScriptContext — V8’s structure for holding closed-over variables — and it gets written on every single invocation.
Here’s the problem. After the first few bit-shifts and & 0xffffffff operations, seed is a 32-bit integer that may not fit in V8’s small integer (Smi) range. Values outside that range get stored as a HeapNumber — a proper heap-allocated object with a pointer. Every assignment to seed traditionally meant allocating a new HeapNumber, copying the value in, and pointing the context slot at it. The old one becomes garbage.
Six writes per Math.random call, called from a tight loop. You’re flooding the allocator and keeping the GC busy for no good reason.
The Fix: Own It, Mutate It
The insight is simple once you see it: if a HeapNumber is only reachable from a single location (in this case, one slot in a ScriptContext), there’s no aliasing problem. No other code can observe the old value through a different pointer. So instead of allocating a fresh object, V8 can mutate the existing HeapNumber in place.
V8 calls these mutable heap numbers. The optimization tracks whether a numeric context variable is exclusively owned — if it is, writes become in-place mutations rather than allocations. The seed variable qualifies perfectly: it’s captured by a single closure and never escapes.
The result is a 2.5x improvement on that benchmark, with a measurable lift to the overall JetStream2 score.
Why This Matters Beyond Benchmarks
The team is careful to note that this pattern shows up in real-world code, not just synthetic tests. Any time you have a numeric accumulator or counter closed over by a frequently-called function, you’re potentially hitting the same wall:
let count = 0;
const tick = () => { count = (count + 1) | 0; };
If count escapes Smi range or if V8 can’t prove it won’t, the old behavior allocated on every write. With mutable heap numbers, that slot gets updated in place.
This is the kind of optimization I find genuinely interesting — not a new algorithm or a dramatic architectural change, but a precise observation about ownership and aliasing that removes work the engine was doing unnecessarily. The benchmark drove the discovery, but the fix is general. Engines get faster not just by running things quicker, but by finding the things they were doing for no reason and stopping.