Created
April 17, 2025 10:35
-
-
Save serid/685743ef526d5fc81b41e7102b3a0b71 to your computer and use it in GitHub Desktop.
A JavaScript to benchmark WASM module linking performance
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=Edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
</head> | |
<body style="background-color:black"> | |
<script src="wasm-link-benchmark.js"></script> | |
</body> | |
</html> |
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
// A JavaScript to benchmark WASM module linking performance | |
console.log("Running in browser: " + (typeof window !== "undefined" && typeof window.document !== "undefined")); | |
console.log("Running in Node: " + (typeof process !== "undefined" && | |
process.versions != null && | |
process.versions.node != null)); | |
// --- Benchmark parameters --- | |
const i32eh = true; | |
function doStuffToAnExportedFunctionBeforeImportingIntoBukiModule(wasmFunctionExportedFromAz) { | |
// return wasmFunctionExportedFromAz; | |
return () => wasmFunctionExportedFromAz(); | |
} | |
// --- WASM Module Definitions (as Uint8Array) --- | |
function bytesFromBase64(a) { | |
return Uint8Array.from(atob(a), c => c.charCodeAt(0)); | |
} | |
// Az Module: Exports a function `addOne` | |
// WAT source: | |
// (module | |
// (func $addOne (result i32) | |
// i32.const 1 | |
// i32.const 2 | |
// i32.add | |
// ) | |
// (export "addOne" (func $addOne)) | |
// ) | |
const azWasmBytes = i32eh ? bytesFromBase64("AGFzbQEAAAABBQFgAAF/AwIBAAcKAQZhZGRPbmUAAAoJAQcAQQFBAmoLABUEbmFtZQEJAQAGYWRkT25lAgMBAAA=") : | |
// same but i64 | |
bytesFromBase64("AGFzbQEAAAABBQFgAAF+AwIBAAcKAQZhZGRPbmUAAAoJAQcAQgFCAnwLABUEbmFtZQEJAQAGYWRkT25lAgMBAAA="); | |
// Buki Module: Imports `importedAddOne` from 'env', exports `callImportedAddOne` | |
// WAT source: | |
// (module | |
// (import "env" "importedAddOne" (func $importedFunc (result i32))) | |
// (func $callImportedAddOne (result i32) | |
// call $importedFunc ;; Call the function imported from Az | |
// ) | |
// (export "callImportedAddOne" (func $callImportedAddOne)) | |
// ) | |
const bukiWasmBytes = i32eh ? bytesFromBase64("AGFzbQEAAAABBQFgAAF/AhYBA2Vudg5pbXBvcnRlZEFkZE9uZQAAAwIBAAcWARJjYWxsSW1wb3J0ZWRBZGRPbmUAAQoGAQQAEAALADEEbmFtZQEjAgAMaW1wb3J0ZWRGdW5jARJjYWxsSW1wb3J0ZWRBZGRPbmUCBQIAAAEA") : | |
// same but i64 | |
bytesFromBase64("AGFzbQEAAAABBQFgAAF+AhYBA2Vudg5pbXBvcnRlZEFkZE9uZQAAAwIBAAcWARJjYWxsSW1wb3J0ZWRBZGRPbmUAAQoGAQQAEAALADEEbmFtZQEjAgAMaW1wb3J0ZWRGdW5jARJjYWxsSW1wb3J0ZWRBZGRPbmUCBQIAAAEA"); | |
// --- Benchmark Setup --- | |
const WARMUP_ITERATIONS = 1_000_000; // Number of iterations for JIT warmup | |
const BENCHMARK_ITERATIONS = 30_000_000; // Number of iterations for measurement | |
async function runBenchmark() { | |
console.log("Compiling WASM modules..."); | |
const azModule = await WebAssembly.compile(azWasmBytes); | |
const bukiModule = await WebAssembly.compile(bukiWasmBytes); | |
console.log("Instantiating Az module..."); | |
const azInstance = await WebAssembly.instantiate(azModule); | |
console.log("Preparing import object for Buki module..."); | |
const importObject = { | |
env: { | |
// Link Buki's import to Az's export | |
importedAddOne: doStuffToAnExportedFunctionBeforeImportingIntoBukiModule(azInstance.exports.addOne), | |
}, | |
}; | |
console.log("Instantiating Buki module with imports from Az..."); | |
const bukiInstance = await WebAssembly.instantiate(bukiModule, importObject); | |
// Get the function we want to benchmark (calls through Buki to Az) | |
const funcToBenchmark = bukiInstance.exports.callImportedAddOne; | |
// --- Warmup Phase --- | |
console.log(`\nRunning warmup (${WARMUP_ITERATIONS.toLocaleString()} iterations)...`); | |
let warmupResult = 0; | |
for (let i = 0; i < WARMUP_ITERATIONS; ++i) { | |
// Pass a changing value to potentially avoid excessive caching effects | |
warmupResult = funcToBenchmark(); | |
} | |
console.log(`Warmup finished. Last result: ${warmupResult}`); // Use result slightly | |
// --- Benchmark Phase --- | |
console.log(`\nRunning benchmark (${BENCHMARK_ITERATIONS.toLocaleString()} iterations)...`); | |
let benchmarkResult = 0; | |
const startTime = performance.now(); | |
for (let i = 0; i < BENCHMARK_ITERATIONS; ++i) { | |
// Call the function that goes Buki -> Az | |
benchmarkResult = funcToBenchmark(); | |
} | |
const endTime = performance.now(); | |
console.log(`Benchmark finished. Last result: ${benchmarkResult}`); // Use result slightly | |
// --- Results --- | |
const totalTimeMs = endTime - startTime; | |
const avgTimeNs = (totalTimeMs / BENCHMARK_ITERATIONS) * 1_000_000; // Convert ms/iter to ns/iter | |
console.log('\n--- Benchmark Results ---'); | |
console.log(`Total iterations: ${BENCHMARK_ITERATIONS.toLocaleString()}`); | |
console.log(`Total time: ${totalTimeMs.toFixed(3)} ms`); | |
console.log(`Average time per call: ${avgTimeNs.toFixed(3)} ns`); // Nanoseconds are often more telling for fast operations | |
console.log('------------------------'); | |
throw new Error | |
} | |
runBenchmark() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment