Skip to content

Instantly share code, notes, and snippets.

@chicoxyzzy
Created July 23, 2026 18:20
Show Gist options
  • Select an option

  • Save chicoxyzzy/4a0332535d2c4a820d0bdef86a39aa3d to your computer and use it in GitHub Desktop.

Select an option

Save chicoxyzzy/4a0332535d2c4a820d0bdef86a39aa3d to your computer and use it in GitHub Desktop.
WebAssembly.Memory.grow portable bench (jsc / node V8) for WebKit 288526
// Portable WebAssembly.Memory.grow microbench for jsc and node/V8.
//
// jsc (BoundsChecking):
// jsc --useWasmFastMemory=false --useExecutableAllocationFuzz=false wasm-memory-grow-bench.js
// jsc (default / Signaling when available):
// jsc --useExecutableAllocationFuzz=false wasm-memory-grow-bench.js
// node / V8:
// node wasm-memory-grow-bench.js
function nowMs() {
if (typeof preciseTime === "function")
return preciseTime() * 1000;
if (typeof performance !== "undefined" && performance.now)
return performance.now();
return Date.now();
}
function median(a) {
a = a.slice().sort((x, y) => x - y);
return a[(a.length - 1) >> 1];
}
function engineName() {
if (typeof preciseTime === "function")
return "jsc";
if (typeof process !== "undefined" && process.versions && process.versions.v8)
return "v8 " + process.versions.v8 + " (node " + process.versions.node + ")";
return "unknown";
}
// Grow-only: create outside the timer, then grow(1).
function benchGrowOnly(N, withMax, opsPerTrial, trials) {
const times = [];
for (let t = 0; t < trials; ++t) {
const mems = [];
for (let i = 0; i < opsPerTrial; ++i) {
mems.push(withMax
? new WebAssembly.Memory({ initial: N, maximum: N + 5 })
: new WebAssembly.Memory({ initial: N }));
}
const t0 = nowMs();
for (let i = 0; i < opsPerTrial; ++i)
mems[i].grow(1);
times.push((nowMs() - t0) / opsPerTrial);
}
return median(times);
}
// One memory: grow page-by-page from 1 to toPages, time only the final grow(1) after reaching N.
function benchGrowAfterStepwise(N, withMax, trials) {
const times = [];
for (let t = 0; t < trials; ++t) {
const m = withMax
? new WebAssembly.Memory({ initial: 1, maximum: N + 5 })
: new WebAssembly.Memory({ initial: 1 });
for (let p = 1; p < N; ++p)
m.grow(1);
const t0 = nowMs();
m.grow(1);
times.push(nowMs() - t0);
}
return median(times);
}
function benchSequence(fromPages, toPages, withMax, trials) {
const steps = toPages - fromPages;
const times = [];
for (let t = 0; t < trials; ++t) {
const m = withMax
? new WebAssembly.Memory({ initial: fromPages, maximum: toPages })
: new WebAssembly.Memory({ initial: fromPages });
const t0 = nowMs();
for (let i = 0; i < steps; ++i)
m.grow(1);
times.push(nowMs() - t0);
}
return median(times);
}
function benchMicro(withMax, iterations, maxPages) {
let total = 0;
const t0 = nowMs();
for (let i = 0; i < iterations; ++i) {
const memory = withMax
? new WebAssembly.Memory({ initial: 1, maximum: maxPages })
: new WebAssembly.Memory({ initial: 1 });
for (let p = 1; p < maxPages; ++p)
total += memory.grow(1);
}
const ms = nowMs() - t0;
const expected = iterations * (maxPages - 1) * maxPages / 2;
if (total !== expected)
throw new Error("bad total " + total + " expected " + expected);
return ms;
}
function fmt(ms) {
if (ms >= 100)
return ms.toFixed(0) + " ms";
if (ms >= 1)
return ms.toFixed(2) + " ms";
if (ms >= 0.001)
return ms.toFixed(4) + " ms";
return (ms * 1000).toFixed(2) + " us";
}
const out = typeof print === "function" ? print : console.log.bind(console);
out("engine: " + engineName());
out("");
out("grow-only ms/op (create outside timer)");
out("N\twith-max\tno-max");
const Ns = [1, 10, 50, 100, 200, 500, 1000, 2500];
for (const N of Ns) {
const ops = N >= 1000 ? 20 : (N >= 100 ? 60 : 150);
const trials = N >= 1000 ? 5 : 7;
out(N + "\t" + fmt(benchGrowOnly(N, true, ops, trials)) + "\t" + fmt(benchGrowOnly(N, false, ops, trials)));
}
out("");
out("grow+1 after stepwise to N (one memory, avoids create@N pool effects)");
out("N\twith-max\tno-max");
for (const N of [100, 1000, 2500]) {
const trials = N >= 1000 ? 12 : 20;
out(N + "\t" + fmt(benchGrowAfterStepwise(N, true, trials)) + "\t" + fmt(benchGrowAfterStepwise(N, false, trials)));
}
out("");
out("100x grow 1->101 total: with-max " + fmt(benchSequence(1, 101, true, 40))
+ " no-max " + fmt(benchSequence(1, 101, false, 15)));
benchMicro(true, 5, 128);
const microMax = [];
const microNo = [];
for (let i = 0; i < 5; ++i)
microMax.push(benchMicro(true, 100, 128));
for (let i = 0; i < 3; ++i)
microNo.push(benchMicro(false, 20, 128) * 5);
microMax.sort((a, b) => a - b);
microNo.sort((a, b) => a - b);
out("micro 100x grow 1->128 wall: with-max med " + fmt(microMax[2])
+ " no-max proxy med " + fmt(microNo[1]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment