import { performance, PerformanceObserver } from 'node:perf_hooks'; import v8 from 'v8-natives'; import * as fs from 'node:fs'; import * as path from 'node:path'; // Credit to https://github.com/milomg/js-reactivity-benchmark for the logic for timing + GC tracking. class GarbageTrack { track_id = 0; observer = new PerformanceObserver((list) => this.perf_entries.push(...list.getEntries())); perf_entries = []; periods = []; watch(fn) { this.track_id++; const start = performance.now(); const result = fn(); const end = performance.now(); this.periods.push({ track_id: this.track_id, start, end }); return { result, track_id: this.track_id }; } /** * @param {number} track_id */ async gcDuration(track_id) { await promise_delay(10); const period = this.periods.find((period) => period.track_id === track_id); if (!period) { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors return Promise.reject('no period found'); } const entries = this.perf_entries.filter( (e) => e.startTime >= period.start && e.startTime < period.end ); return entries.reduce((t, e) => e.duration + t, 0); } destroy() { this.observer.disconnect(); } constructor() { this.observer.observe({ entryTypes: ['gc'] }); } } function promise_delay(timeout = 0) { return new Promise((resolve) => setTimeout(resolve, timeout)); } /** * @param {{ (): void; (): any; }} fn */ function run_timed(fn) { const start = performance.now(); const result = fn(); const time = performance.now() - start; return { result, time }; } /** * @param {() => void} fn */ async function run_tracked(fn) { v8.collectGarbage(); const gc_track = new GarbageTrack(); const { result: wrappedResult, track_id } = gc_track.watch(() => run_timed(fn)); const gc_time = await gc_track.gcDuration(track_id); const { result, time } = wrappedResult; gc_track.destroy(); return { result, timing: { time, gc_time } }; } /** * @param {number} times * @param {() => void} fn */ export async function fastest_test(times, fn) { const results = []; for (let i = 0; i < times; i++) { const run = await run_tracked(fn); results.push(run); } const fastest = results.reduce((a, b) => (a.timing.time < b.timing.time ? a : b)); return fastest; } /** * @param {boolean} a */ export function assert(a) { if (!a) { throw new Error('Assertion failed'); } } /** * @param {string} file */ export function read_file(file) { return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); } /** * @param {string} file * @param {string} contents */ export function write(file, contents) { try { fs.mkdirSync(path.dirname(file), { recursive: true }); } catch {} fs.writeFileSync(file, contents); }