UNPKG

tenko

Version:

A "pixel perfect" 100% spec compliant ES2021 JavaScript parser written in JS.

217 lines (194 loc) 7.37 kB
// Benchmark core file. This file basically gets called with N=x and will execute a parse test for one file for that step // I run this through `./t p --build`. This will make ./t call this script in an indefinite loop, passing on incrementing // values for N until it exits non-zero, and then loops again from N=1. Infinitely. // You can call this script directly // (This is not the same because the same node instance is used for all tests so the parsers pollute the GC for each other!); // node --experimental-modules --max-old-space-size=8192 tests/perf.mjs -b // node --single-threaded --single-threaded-gc --predictable --predictable-gc-schedule --no-compilation-cache --experimental-modules --max-old-space-size=8192 tests/perf.mjs -b import fs from 'fs'; import {performance} from 'perf_hooks'; import path from 'path'; const TENKO_DEV_FILE = path.resolve('./src/index.mjs'); const TENKO_PROD_FILE = path.resolve('./build/tenko.prod.mjs'); let filePath = import.meta.url.replace(/^file:\/\//,''); let dirname = path.dirname(filePath); const RESET_BASELINE = process.argv.includes('--reset'); const IMPROVE_BASELINE = process.argv.includes('--record'); let N = process.argv.includes('n') ? parseInt(process.argv[process.argv.indexOf('n') + 1]) : -1; const Nstart = N; const NO_HEADER = process.argv.includes('--no-header'); const NO_WEBKIT = process.argv.includes('-q'); // Skip the 20mb benchmark, which takes a long time if (!Number.isInteger(N)) throw new Error('The argument for `n` must be a number'); const BOLD = '\x1b[;1;1m'; const BOLD_GREEN = '\x1b[1;32m'; const DIM = '\x1b[30;1m'; const DIM_RED = '\x1b[;31m'; const DIM_GREEN = '\x1b[;32m'; const RED = '\x1b[31m'; const GREEN = '\x1b[32m'; const RESET = '\x1b[0m'; let parserCache = new Map let getParser = async (name) => { if (parserCache.has(name)) return name; let parser; switch (name) { case 'acorn': let {runAcorn} = await import('./run_acorn.mjs'); parser = runAcorn; break; case 'babel': let {runBabel} = await import('./run_babel.mjs'); parser = runBabel; break; case 'meriy': let {runMeriyah} = await import('./run_meriyah.mjs'); parser = runMeriyah; break; case 'ghost': let {runGhost} = await import('./run_ghost.mjs'); parser = runGhost; break; case 'tenko': case 'tenk2': let {Tenko} = await import(TENKO_PROD_FILE); let {testTenko} = await import('./parse_tenko.mjs') parser = (code, testVariant, annexb, version) => testTenko(Tenko, code, testVariant, annexb, version); break; default: throw 'unknown parser? [' + name + ']'; } parserCache.set(name, parser); return parser; }; let parsers = [ 'tenko', 'babel', 'acorn', 'meriy', 'ghost', 'tenk2' ]; // See ./perf_data.json for example. I tuck it under /ignore because webstorm goes haywire trying to reindex the file while running the benchmark and updating it // { // "es6.tiny": { // just a name // "path": "ignore/perf/es6.tiny.js", // path // "mode": "module", // web | module // "baseline": { // one entry for each parser (will be fine if its missing) // "tenko": 3, // "babel": 4, // "acorn": 3, // "tenk2": 3, // ... // } // }, // ... let codeCache = new Map let data = JSON.parse(fs.readFileSync(path.join(dirname, '../ignore/perf_data.json'), 'utf8')); let getcode = (fpath) => { if (codeCache.has(fpath)) return codeCache.get(fpath); let code = fs.readFileSync(fpath, 'utf8'); codeCache.set(fpath, code); return code; } let files = Object.getOwnPropertyNames(data).filter(s => !!s && (!NO_WEBKIT || s !== 'es5.webkit')); function p2(x) { return Math.round(x * 100) / 100; } let cols = { parser: 25, prev: 7, base: 7, btd: 10, time: 20, td: 17, dprev: 25, }; function print(name, baseline, time, lastTtime) { let base = baseline[name]; let tbase = baseline.tenko | 0; let btd = tbase - base; let ftime = Math.floor(time); let td = ftime - base; let ltd = Math.floor(time - (lastTtime|0)); console.log( // Title N >= 0 ? DIM + 'n=' + String(Nstart).padStart(2, '0') + RESET : '', ('Parse time for ' + (name === 'tenk2' ? 'tenko' : name) + ': ').padEnd(cols.parser, ' '), // Basline for this parser DIM + (''+(name === 'tenko' ? lastTtime : '')).padStart(cols.prev, ' ') + RESET, DIM + (''+base).padStart(cols.base, ' ') + RESET, // Delta between baseline of other parsers and baseline of Tenko DIM + (name === 'tenko' ? '' : (btd > 0 ? '+' : '') + btd + '').padStart(cols.btd, ' ') + RESET, // Current result of current parser (time + ' ms').padStart(cols.time, ' '), // Delta between current result and baseline result (of current parser) (td < 0 ? (name === 'tenko' ? BOLD_GREEN : DIM_GREEN) : name === 'tenko' ? BOLD : DIM) + (' (b ' + (td > 0 ? '+' : '-') + ' ' + Math.abs(td) + ' ms)').padStart(cols.td, ' ') + RESET, ' ', DIM + (name === 'tenko' ? ' ' + Math.floor(time) + 'ms' : (' ' + (lastTtime + ' ' + (ltd > 0 ? '+' : '-')) + ' ' + (name.startsWith('tenk') ? '' : ltd >= 0 ? DIM_GREEN : DIM_RED) + Math.abs(ltd) + 'ms').padEnd(cols.dprev, ' ')) + RESET, '' ); } if (!NO_HEADER && N <= 1 && N != -2) { console.log('Using parser from:', TENKO_PROD_FILE); console.log('\n'); console.log(' ', 'parser '.padStart(cols.parser, ' '), 'prev'.padStart(cols.prev + 3, ' '), 'base'.padStart(cols.base, ' '), 'btd'.padStart(cols.btd, ' '), 'time'.padStart(cols.time, ' '), 'td'.padStart(cols.td, ' '), 'dprev'.padStart(cols.dprev-10, ' '), '\n' ); } // Block process (async function main(){ for (let i=0; i<files.length; ++i) { let ftitle = files[i]; let obj = data[ftitle]; if (N <= 1) { console.log('File:', getcode(obj.path).length, 'bytes:', obj.path); } for (let j=0; j<parsers.length; ++j) { if (--N > 0) continue; let pname = parsers[j]; let parser = await getParser(pname); let {path, mode, baseline, last} = obj; let t1 = performance.now(); try { parser(getcode(path), mode, true, 11); // enable webcompat by default } catch (e) { console.error(RED + pname + ' crashed :( ' + DIM + String(e.message).slice(0, 200) + RESET); if (N >= 0) process.exit(0); } let t2 = performance.now(); let time = t2 - t1; let b = baseline[pname]; let t = p2(time); print(pname, baseline, t, last); let changedStats = false; if (RESET_BASELINE || (IMPROVE_BASELINE && t < b)) { baseline[pname] = Math.floor(t); changedStats = true; } if (pname === 'tenko') { obj.last = Math.floor(t); changedStats = true; } if (N >= 0) { if (changedStats) { fs.writeFileSync('ignore/perf_data.json', JSON.stringify(data)); } // Signal ./t that this script is not finished, just the current run process.exit(0); } } } if (N >= 0) { // signals ./t that this script is done // console.log('exit(1)') process.exit(1); } })().catch((e) => console.log('perf.mjs main promise threw an error:', e)).then(() => { process.exit(0); });