compare.mjs (4337B)
1 // Fresh-process paired smoke, not a browser performance claim. 2 import assert from 'node:assert/strict'; 3 import {readFile, writeFile} from 'node:fs/promises'; 4 import {spawnSync} from 'node:child_process'; 5 import {createHash} from 'node:crypto'; 6 import {cpus} from 'node:os'; 7 import {WasmRuntime} from './runtime.mjs'; 8 import {Graph} from '../demo-site/dist/graph.js'; 9 import {decodeBundle,loadBundle} from '../demo-site/dist/bundle.js'; 10 import {text,readText} from '../demo-site/dist/codecs.js'; 11 import {compiledDefinitions} from '../demo-site/dist/compiler.js'; 12 const [wasmPath, output, host, job] = process.argv.slice(2); 13 const fixtures = JSON.parse(await readFile('ext/demo-site/test/compiler-conformance.json')); 14 const cases = ['identity','append','identity-bulk','append-bulk','identity-text','append-text', 'compiler-0','compiler-1','compiler-2','compiler-3']; 15 if (host) { 16 const start = performance.now(); 17 const g = host === 'wasm' ? await WasmRuntime.create(await readFile(wasmPath)) 18 : new Graph({nodes:6_000_000,fuel:Infinity,stack:100_000},{threshold:250_000}); 19 const compiler = job.startsWith('compiler-'); 20 const bulk = host === 'wasm' && job.endsWith('-bulk'); 21 const scalar = host === 'wasm' && job.endsWith('-text'); 22 const bytes = await readFile(compiler ? `ext/demo-site/bundles/${fixtures.compilerBundleSha256}.arboricx` 23 : `test/fixtures/${job.startsWith('identity') ? 'id' : 'append'}.arboricx`); 24 const name = compiler ? fixtures.export : undefined; 25 const root = host === 'wasm' ? g.load(bytes,name) : (() => { 26 const b = decodeBundle(bytes), exports = loadBundle(g,b); 27 return exports.get(name ?? b.exports[0].name); 28 })(); 29 const loaded = performance.now(); 30 let result, expected; 31 if (compiler) { 32 const c = fixtures.cases[Number(job.split('-')[1])]; 33 result = g.apply(root,[g.leaf,host==='wasm'?g.ofText(c.source,16384):text(g,c.source)]); expected = c; 34 } else { 35 const a = (job.endsWith('-text')?'λ雪🙂':'abc012').repeat(100), b = (job.endsWith('-text')?'é\0🦊':'XYZ789').repeat(100); 36 result = g.apply(root, (job.startsWith('identity') ? [a] : [a,b]).map(x => scalar ? g.ofText(x) : bulk ? g.ofBytes(new TextEncoder().encode(x)) : text(g,x))); 37 expected = job.startsWith('identity') ? a : a+b; 38 } 39 const constructed = performance.now(); 40 if (compiler) { 41 const defs = compiledDefinitions(g,result); 42 assert.deepEqual([...defs.keys()].sort(),Object.keys(expected.definitions).sort()); 43 for (const [name, tree] of Object.entries(expected.definitions)) assert.equal(g.toTernary(defs.get(name)),tree); 44 assert.equal(g.toTernary(g.apply(defs.get(expected.export),expected.arguments.map(x=>g.fromTernary(x)))),expected.result); 45 } else assert.equal(scalar ? g.toText(result) : bulk ? new TextDecoder('utf-8',{fatal:true}).decode(g.toBytes(result)) : readText(g,result),expected); 46 const end = performance.now(); 47 console.log(JSON.stringify({host,job,loadMs:loaded-start,constructMs:constructed-loaded, 48 evaluateObserveMs:end-constructed,totalMs:end-start,peakRssKiB:process.resourceUsage().maxRSS, 49 wasmMemoryBytes:host==='wasm'?g.e.memory.buffer.byteLength:null, 50 fixtureSha256:createHash('sha256').update(bytes).digest('hex')})); 51 g.dispose?.(); 52 } else { 53 const samples=[]; 54 for (const job of cases) for(let round=0;round<3;round++) { 55 for (const host of round%2 ? ['wasm','js'] : ['js','wasm']) { 56 const child=spawnSync(process.execPath,['--max-old-space-size=512',import.meta.filename,wasmPath,'-',host,job], 57 {encoding:'utf8',timeout:120_000,maxBuffer:1024*1024}); 58 if(child.error || child.status!==0) throw new Error(`${job}/${host}: ${child.error ?? child.stderr}`); 59 samples.push({...JSON.parse(child.stdout),round}); 60 } 61 } 62 const report={schema:1,node:process.version,cpu:cpus()[0]?.model,platform:process.platform, 63 wasmSha256:createHash('sha256').update(await readFile(wasmPath)).digest('hex'), 64 optimization:'ReleaseSafe',samples}; 65 await writeFile(output,JSON.stringify(report,null,2)+'\n'); 66 for (const job of cases) { 67 const median=h=>samples.filter(s=>s.job===job&&s.host===h).map(s=>s.evaluateObserveMs).sort((a,b)=>a-b)[1]; 68 console.log(`${job}: JS ${median('js').toFixed(1)} ms; WASM ${median('wasm').toFixed(1)} ms; ${(median('js')/median('wasm')).toFixed(2)}x`); 69 } 70 }