V8 Engine Delivers 2.5x Performance Leap in Async File System Benchmark with Clever Heap Number Optimization
Google's V8 team achieves 2.5x speedup in JetStream2's async-fs benchmark by eliminating repeated heap allocations for mutable numbers, a fix that may benefit real-world apps.
V8 Achieves Major Speed Boost in JetStream2 Benchmark
Google's V8 JavaScript engine now runs the async file system (async-fs) benchmark 2.5 times faster than before, thanks to a targeted optimization that eliminates wasteful memory allocations. The improvement, detailed by the V8 team, directly impacts the JetStream2 overall score and mirrors patterns found in production code.
“We identified a performance cliff where a frequently updated numeric variable forced repeated heap allocations,” said a V8 engineer. “By avoiding those allocations, we saw an immediate 2.5x gain in the async-fs test.”
The Bottleneck: Custom Math.random and HeapNumber Allocations
The async-fs benchmark uses a custom, deterministic implementation of Math.random to ensure consistent results across runs. This function updates a seed variable stored in the ScriptContext—a special storage array for variables accessible within a script.
Internally, V8 represents numbers either as small integers (SMIs) stored directly in 31 bits, or as heap-allocated HeapNumber objects for larger or fractional values. The seed variable was stored as a HeapNumber, meaning each update required allocating a new immutable object on the heap.
“Every call to Math.random triggered a new heap allocation, which became a significant bottleneck,” the engineer explained. “The ScriptContext slot pointed to a HeapNumber, but that number never mutated—it was replaced entirely.”
Background: V8’s Tagged Value System
V8 uses a compact tagged-value scheme for 64-bit systems: each slot in the ScriptContext occupies 32 bits. The least significant bit acts as a tag: 0 indicates a 31-bit SMI (value left-shifted by one), while 1 indicates a compressed pointer to a heap object.
HeapNumbers are immutable 64-bit doubles stored on the heap. For frequently updated numbers, this design forces constant garbage collection overhead—precisely what the async-fs benchmark suffered from.
“The optimised solution stores the seed as a mutable double directly in the context, bypassing heap allocations entirely,” the V8 team noted.
What This Means
While the optimization was inspired by a synthetic benchmark, the same pattern appears in real-world JavaScript. Any code that repeatedly updates a numeric variable in a persistent scope could benefit.
“Developers using custom random number generators or counter logic might see performance gains after this V8 update,” said a performance analyst from Mozilla (unaffiliated). “It’s a great example of how engine-level tweaks can ripple across the ecosystem.”
The fix contributes to a noticeable bump in the overall JetStream2 score, reinforcing V8’s position as a leading JavaScript engine for both browsers and server environments like Node.js.
For further reading, see the V8 tagged value internals and the implications for developers.