Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/jz-kernel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# JZ kernel example

[`jz`](https://www.npmjs.com/package/jz) compiles a JavaScript-subset numeric kernel to standard WebAssembly. EdgeJS loads it through `WebAssembly.Module` / `WebAssembly.Instance`.

```bash
npm ci
edge bench.mjs
```

`bench.mjs` runs a small `Float64Array` 4x4 matrix kernel through one compiled export and prints checksum, import count, byte size, and median timings for raw JS vs JZ WASM. Tune with `ITERATIONS`, `WARMUP`, `RUNS`.

`index.mjs` is a one-shot smoke check: `edge index.mjs`.

JZ fits hot numeric, DSP, parser, and typed-array kernels. It is not a JavaScript runtime.
126 changes: 126 additions & 0 deletions examples/jz-kernel/bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import assert from 'node:assert/strict';
import { compile } from 'jz';

const kernelSource = `
const init = (a, b) => {
for (let i = 0; i < 16; i++) {
a[i] = (i + 1) * 0.125
b[i] = (16 - i) * 0.0625
}
}

const multiplyMany = (a, b, out, iters) => {
for (let n = 0; n < iters; n++) {
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
let s = 0
for (let k = 0; k < 4; k++) s += a[r * 4 + k] * b[k * 4 + c]
out[r * 4 + c] = s + n * 0.0000001
}
}
const t = a[0]
a[0] = out[15]
a[5] = t + out[10] * 0.000001
}
}

export let run = (iters) => {
const a = new Float64Array(16)
const b = new Float64Array(16)
const out = new Float64Array(16)
init(a, b)
multiplyMany(a, b, out, iters)
let checksum = 0
for (let i = 0; i < 16; i++) checksum = (checksum + out[i] * 1000000) | 0
return checksum
}
`;

function init(a, b) {
for (let i = 0; i < 16; i += 1) {
a[i] = (i + 1) * 0.125;
b[i] = (16 - i) * 0.0625;
}
}

function multiplyMany(a, b, out, iters) {
for (let n = 0; n < iters; n += 1) {
for (let r = 0; r < 4; r += 1) {
for (let c = 0; c < 4; c += 1) {
let s = 0;
for (let k = 0; k < 4; k += 1) s += a[r * 4 + k] * b[k * 4 + c];
out[r * 4 + c] = s + n * 0.0000001;
}
}
const t = a[0];
a[0] = out[15];
a[5] = t + out[10] * 0.000001;
}
}

function runJs(iters) {
const a = new Float64Array(16);
const b = new Float64Array(16);
const out = new Float64Array(16);
init(a, b);
multiplyMany(a, b, out, iters);
let checksum = 0;
for (let i = 0; i < 16; i += 1) checksum = (checksum + out[i] * 1000000) | 0;
return checksum;
}

const wasm = compile(kernelSource);
const mod = new WebAssembly.Module(wasm);
const imports = WebAssembly.Module.imports(mod);
const { exports } = new WebAssembly.Instance(mod);

assert.deepEqual(imports, []);
assert.equal(exports.run(128), runJs(128));

function positiveInt(name, fallback) {
const value = Number(process.env[name] || fallback);
assert(Number.isInteger(value) && value > 0, `${name} must be a positive integer`);
return value;
}

const iterations = positiveInt('ITERATIONS', 200000);
const warmup = positiveInt('WARMUP', 5);
const runs = positiveInt('RUNS', 9);
const now = globalThis.performance && typeof globalThis.performance.now === 'function'
? () => globalThis.performance.now()
: () => Date.now();

function median(values) {
const sorted = values.slice().sort((a, b) => a - b);
return sorted[sorted.length >> 1];
}

function measure(fn) {
for (let i = 0; i < warmup; i += 1) fn(iterations);
const samples = [];
let checksum = 0;
for (let i = 0; i < runs; i += 1) {
const sampleStart = now();
checksum = fn(iterations);
samples.push(now() - sampleStart);
}
return {
checksum,
medianMs: Number(median(samples).toFixed(3)),
};
}

const js = measure(runJs);
const jzWasm = measure(exports.run);

assert.equal(jzWasm.checksum, js.checksum);

console.log(JSON.stringify({
iterations,
warmup,
runs,
imports: imports.length,
wasmBytes: wasm.length,
js,
jzWasm,
}, null, 2));
37 changes: 37 additions & 0 deletions examples/jz-kernel/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from 'node:assert/strict';
import { compile } from 'jz';

const kernelSource = `
export let mac = (a, b, c) => {
let x = c
for (let i = 0; i < 8; i++) x = x + (a + i) * (b - i)
return x
}

export let mix = (x, y) => mac(x, y, 3) - mac(y, x, 1)
`;

function macJs(a, b, c) {
let x = c;
for (let i = 0; i < 8; i += 1) x += (a + i) * (b - i);
return x;
}

const wasm = compile(kernelSource);
const mod = new WebAssembly.Module(wasm);

assert.deepEqual(WebAssembly.Module.imports(mod), []);

const { exports } = new WebAssembly.Instance(mod);

for (const [a, b, c] of [[2, 9, 1], [7, 4, 3], [11, -2, 5]]) {
assert.equal(exports.mac(a, b, c), macJs(a, b, c));
}
assert.equal(exports.mix(4, 7), macJs(4, 7, 3) - macJs(7, 4, 1));

console.log(JSON.stringify({
imports: 0,
wasmBytes: wasm.length,
mac: exports.mac(2, 9, 1),
mix: exports.mix(4, 7),
}, null, 2));
41 changes: 41 additions & 0 deletions examples/jz-kernel/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions examples/jz-kernel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "edgejs-jz-kernel-example",
"private": true,
"type": "module",
"scripts": {
"start": "edge index.mjs",
"bench": "edge bench.mjs"
},
"dependencies": {
"jz": "^0.1.1"
}
}
Loading