# Turbocharging V8 with mutable heap numbers

DevFeed: [Turbocharging V8 with mutable heap numbers](<https://devfeed.tech/articles/turbocharging-v8-with-mutable-heap-numbers-3530.md>)

Original publisher: [Read original article](<https://v8.dev/blog/mutable-heap-number>)

Author: Victor Gomes, the bit shifter

Published: 2025-02-25T00:00:00Z

Content type: article

Language: en

Sources: [V8](<https://devfeed.tech/sources/v8.md>)

Topics: [Code](<https://devfeed.tech/topics/code.md>)

Tags: [benchmark](<https://devfeed.tech/tags/benchmark.md>), [benchmarks](<https://devfeed.tech/tags/benchmarks.md>), [internals](<https://devfeed.tech/tags/internals.md>), [javascript](<https://devfeed.tech/tags/javascript.md>), [optimization](<https://devfeed.tech/tags/optimization.md>), [performance](<https://devfeed.tech/tags/performance.md>), [random](<https://devfeed.tech/tags/random.md>)

## AI overview

This article explains a V8 optimization for mutable heap numbers that improves JavaScript performance in the JetStream2 async-fs benchmark by reducing allocation pressure and enabling faster integer arithmetic.

## Source excerpt

At V8, we're constantly striving to improve JavaScript performance. As part of this effort, we recently revisited the JetStream2 benchmark suite to eliminate performance cliffs. This post details a specific optimization we made that yielded a significant 2.5x improvement in the async-fs benchmark, contributing to a noticeable boost in the overall score. The optimization was inspired by the benchmark, but such patterns do appear in real-world code. The target async-fs and a peculiar Math.random # The async-fs benchmark, as its name suggests, is a JavaScript file system implementation, focusing on asynchronous operations. However, a surprising performance bottleneck exists: the implementation of Math.random. It uses a custom, deterministic implementation of Math.random for consistent results across runs. The implementation is: 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 key variable here is seed. It's updated on every call to Math.random, generating the pseudo-random sequence. Crucially, here seed is stored in a ScriptContext. A ScriptContext serves as a storage location for values accessible within a particular script. Internally, this context is represented as an array of V8's tagged values. On the default V8 configuration for 64-bit systems, each of these tagged values occupies 32 bits. The least significant bit of each value acts as a tag. A 0 indicates a 31-bit Small Integer (SMI). The actual integer value is stored directly, left-shifted by one bit. A 1 indicates a compressed pointer to a heap object, where the compressed pointer value is incremented by o