import "core-js/stable/array/fill.js"; import faker from "faker"; import { VNode } from "../../package/vnode"; import { h } from "../../package/h"; import { init as curInit } from "../../package/init"; import { init as refInit } from "latest-snabbdom-release/init"; import { assert } from "chai"; import pReduce from "p-reduce"; import pMapSeries from "p-map-series"; import { std, mean } from "mathjs"; const RUNS = 5; const PATCHES_PER_RUN = 100; const WARM_UP_RUNS = 1; const REQUEST_ANIMATION_FRAME_EVERY_N_PATCHES = 1; const BENCHMARK_TIMEOUT_MINUTES = 10; const REQUIRED_PRECISION = 0.02; /* eslint-disable @typescript-eslint/no-unused-vars */ declare global { // eslint-disable-next-line @typescript-eslint/naming-convention const __karma__: { info(info: unknown): void; }; } /* eslint-enable @typescript-eslint/no-unused-vars */ const ALLOWED_REGRESSION = 0.03; describe("core benchmark", () => { it("does not regress", async function Benchmark() { this.timeout(BENCHMARK_TIMEOUT_MINUTES * 1000 * 60); faker.seed(0); const inputs = Array(PATCHES_PER_RUN) .fill(null) .map(() => { return new Array(faker.random.number(20)).fill(null).map(() => ({ name: faker.company.companyName(), catchPhrase: faker.company.catchPhrase(), suffix: faker.company.companySuffix(), products: Array(faker.random.number(3)) .fill(null) .map(() => ({ name: faker.commerce.productName(), color: faker.commerce.color(), price: faker.commerce.price() + faker.finance.currencySymbol(), })), founded: faker.date.past(), })); }); type Input = typeof inputs[0]; const view = (companies: Input): VNode => h("table", [ h("caption", ["Companies"]), h("thead", [ h( "tr", ["Details", "Products"].map((th) => h("th", [th])) ), ]), h( "tbody", companies.map(function companyView(company) { return h("tr", [ h("td", [ h("div", [ h("b", [company.name]), company.suffix && `\xa0${company.suffix}`, ]), h("div", h("i", [company.catchPhrase])), h("td", [ h("dt", ["Founded"]), h("dd", [company.founded.toLocaleDateString()]), ]), ]), h("td", [ h( "ul", company.products.map(function productView(product) { return h("li", [ h("dl", [ h("dt", ["Name"]), h("dd", [product.name]), h("dt", ["Color"]), h("dd", [product.color]), h("dt", ["Price"]), h("dd", [product.price]), ]), ]); }) ), ]), ]); }) ), ]); type Patcher = ReturnType; interface SingleRunResult { i: number; cur: number; ref: number; } const subjectToResult = async ( subject: Patcher, subjectId: string ): Promise => { await new Promise((resolve) => { requestAnimationFrame(resolve); }); const markName = `mark:${subjectId}`; const measureName = `measure:${subjectId}`; performance.mark(markName); const lastVnode = await pReduce( inputs, async function subjectToResultReducer( acc: HTMLElement | VNode, input, i ) { const vnode = view(input); subject(acc, vnode); if (i % REQUEST_ANIMATION_FRAME_EVERY_N_PATCHES === 0) { await new Promise((resolve) => { requestAnimationFrame(resolve); }); } return vnode; }, document.body.appendChild(document.createElement("section")) ); performance.measure(measureName, markName); if (!("elm" in lastVnode)) throw new Error(); if (!lastVnode.elm) throw new Error(); document.body.removeChild(lastVnode.elm); const measure = performance.getEntriesByName(measureName)[0]; performance.clearMarks(markName); performance.clearMeasures(measureName); return measure.duration; }; const singleRun = async ( _: null, runI: number ): Promise => { const cur = await subjectToResult(curInit([]), `cur:${runI}`); const ref = await subjectToResult(refInit([]), `ref:${runI}`); return { i: runI, cur, ref }; }; const runResults = ( await pMapSeries(Array(RUNS + WARM_UP_RUNS).fill(null), singleRun) ).slice(WARM_UP_RUNS); __karma__.info({ benchmark: runResults }); const results = { ref: runResults.map((result) => result.ref), cur: runResults.map((result) => result.cur), }; const means = { ref: mean(results.ref), cur: mean(results.cur), }; const stds = { ref: std(results.ref, "uncorrected"), cur: std(results.cur, "uncorrected"), }; (["ref", "cur"] as const).forEach((subject) => { const stdRatio = stds[subject] / means[subject]; assert.isAtMost( stdRatio, REQUIRED_PRECISION, `${subject} not precise enough` ); }); assert.isAtMost(means.cur, means.ref * (1 + ALLOWED_REGRESSION)); }); });