Created
July 23, 2026 18:20
-
-
Save chicoxyzzy/4a0332535d2c4a820d0bdef86a39aa3d to your computer and use it in GitHub Desktop.
WebAssembly.Memory.grow portable bench (jsc / node V8) for WebKit 288526
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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