V8's Mutable Heap Numbers: A 2.5x Speed Boost for JavaScript

By

Introduction

At V8, performance is an ongoing mission. Recently, the team targeted the JetStream2 benchmark suite to identify and remove performance bottlenecks. One optimization, inspired by the async-fs benchmark, resulted in a remarkable 2.5x improvement for that specific test and a noticeable lift in the overall score. While the benchmark triggered the change, similar patterns appear in real-world JavaScript code.

V8's Mutable Heap Numbers: A 2.5x Speed Boost for JavaScript
Source: v8.dev

The async-fs Benchmark and Its Math.random

The async-fs benchmark implements a JavaScript file system with asynchronous operations. Surprisingly, its main performance culprit was the custom Math.random implementation. To guarantee reproducible results, the benchmark uses a deterministic pseudo-random number generator:

let seed;
Math.random = (function() {
  return function () {
    seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
    seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
    seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
    seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
    seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
    seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
    return (seed & 0xfffffff) / 0x10000000;
  };
})();

The variable seed is updated on every call, generating the pseudo‑random sequence. Critically, seed is stored in a ScriptContext, a storage area for variables accessible within a script.

Understanding ScriptContext and Number Storage

Internally, V8 represents a ScriptContext as an array of tagged values. On 64‑bit systems (default configuration), each tagged value occupies 32 bits. The least significant bit acts as a tag:

This tagging scheme determines how numbers are stored:

The ScriptContext layout includes slots for metadata, the global object (NativeContext), and variables like seed. An untagged double‑precision value occupies its own slot. This design efficiently handles various numeric types while optimizing for the common SMI case.

The Performance Bottleneck

Profiling the custom Math.random exposed two major issues:

  1. HeapNumber allocation: The slot for the seed variable points to an immutable HeapNumber. Every time Math.random updates seed, a new HeapNumber object must be allocated on the heap. This allocation cost adds up quickly, especially when Math.random is called frequently.
  2. No in-place mutation: Because HeapNumbers are immutable, updating seed cannot happen in place. The engine must create a fresh object and update the pointer, causing memory management overhead.

In the async-fs benchmark, the heavy use of Math.random for file system operations (e.g., generating unique IDs) made this bottleneck significant.

The Optimization: Mutable Heap Numbers

The V8 team introduced a new mechanism: mutable heap numbers. Instead of replacing the entire HeapNumber when seed changes, the engine now allows the existing HeapNumber object to be mutated in place—its double value can be overwritten. This eliminates the allocation and garbage collection pressure.

The key changes include:

This optimization was inspired by the benchmark but naturally applies to any hot code that repeatedly updates a numeric variable in a context, such as counters, accumulators, or generators.

Impact and Conclusion

After implementing mutable heap numbers, the async-fs benchmark showed a 2.5x speedup. The overall JetStream2 score also improved noticeably. This case illustrates how profiling pinpointed a seemingly small issue—HeapNumber allocation in a script context—that caused a large performance cliff.

V8’s approach to mutable heap numbers demonstrates a pragmatic tradeoff: sacrificing immutability in a strictly controlled internal case to gain runtime efficiency. Such optimizations, while triggered by benchmarks, often translate to real‑world benefits for JavaScript applications that perform heavy numeric computations.

For more details on V8’s performance improvements, see related articles on number storage and bottleneck analysis.

Tags:

Related Articles

Recommended

Discover More

How to Close the AI Joy Gap: A Developer's Guide to Thriving in the AI EraBohmian Mechanics: A Realistic Take on Quantum Reality and How to Test ItReact Native 0.82: Everything You Need to Know About the New Architecture-Only ReleaseRevolutionary DNA Therapy Slashes 'Bad' Cholesterol by Half, Bypassing StatinsMagnetic Fields Restore Superconductivity in Nickelates: Q&A