tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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 }