// Round-14 frontend micro-pack (benches/ROUND14.md §F1, §F2). // // Each section is BEFORE (verbatim replica of the shipped shape) vs AFTER // (proposed shape), with an equivalence gate and a wall-time perf gate — the // same discipline as the Rust micro-packs: an AFTER that doesn't beat its // BEFORE fails the gate. import { describe, expect, it } from 'vitest'; // ──────────────────────────────────────────────────────────────────────────── // [F2] favorites `favoriteIds` — rebuild-a-fresh-Set-per-page vs incremental // ──────────────────────────────────────────────────────────────────────────── // // Audit finding: the favorites route derived `favoriteIds = new SvelteSet( // items.map(i => i.id))`. Every infinite-scroll page (`raw = [...raw, ...page]`) // rebuilt a brand-new set over the WHOLE accumulated list — O(N) per page, // O(N²) across a P-page drain — and, being a new instance each page, // invalidated every mounted star reader. The fix keeps one persistent set and // `add`s only the fresh page's ids (clear on reset). Since every item on the // page is a favorite and removed items aren't rendered, the set only has to be // a superset of the displayed ids, so `add`-only is correct. /** A page of ids (50/page, the default page size). */ function pageOf(start: number, n: number): string[] { return Array.from({ length: n }, (_, i) => `fav-${start + i}`); } /** BEFORE: rebuild a fresh Set over the whole accumulated list each page. */ function rebuildPerPage(pages: string[][]): Set { let acc: string[] = []; let set = new Set(); for (const page of pages) { acc = [...acc, ...page]; // the route's `raw = [...raw, ...page]` set = new Set(acc.map((id) => id)); // new instance + O(N) rebuild } return set; } /** AFTER: one persistent set, add only the fresh page's ids. */ function incrementalPerPage(pages: string[][]): Set { const set = new Set(); for (const page of pages) { for (const id of page) set.add(id); } return set; } describe('round14 §F2 — favorites favoriteIds incremental set', () => { it('final membership is identical (equivalence gate)', () => { const pages = Array.from({ length: 20 }, (_, p) => pageOf(p * 50, 50)); const before = rebuildPerPage(pages); const after = incrementalPerPage(pages); expect(after.size).toBe(before.size); for (const id of before) expect(after.has(id)).toBe(true); for (const id of after) expect(before.has(id)).toBe(true); }); it('a P-page drain builds the set ≥5x faster incrementally (perf gate)', () => { const PAGES = 40; const PER = 50; // 2 000 items total const pages = Array.from({ length: PAGES }, (_, p) => pageOf(p * PER, PER)); const run = (f: (p: string[][]) => Set): number => { const t0 = performance.now(); // A few repetitions so the measurement isn't dominated by timer noise. for (let r = 0; r < 20; r++) f(pages); return performance.now() - t0; }; // Warm-up (JIT) then measure. run(rebuildPerPage); run(incrementalPerPage); const beforeMs = run(rebuildPerPage); const afterMs = run(incrementalPerPage); console.info( `§F2 ${PAGES} pages × ${PER}: rebuild-per-page ${beforeMs.toFixed(1)} ms vs incremental ${afterMs.toFixed(1)} ms (${(beforeMs / afterMs).toFixed(1)}x)` ); expect(afterMs).toBeLessThan(beforeMs / 5); }); }); // ──────────────────────────────────────────────────────────────────────────── // [F1] t() params — throwaway `{}` per call vs a shared frozen empty object // ──────────────────────────────────────────────────────────────────────────── // // The ubiquitous inline-fallback form `t('k', 'Fallback')` and the bare // `t('k')` (default param `= {}`) allocated a fresh params object on every // call, though for a cache-hit string with no `{{…}}` `interpolate` returns // before ever reading params. t() runs ~10×/row. The fix hoists a shared // frozen `EMPTY_PARAMS` for both no-param branches. const EMPTY_PARAMS: Record = Object.freeze({}); /** Model of the shipped t() param selection + a representative params read * (interpolate's `params[name]` lookup), isolated from dictionary I/O. */ function tBefore(paramsOrFallback: string | Record = {}): unknown { const isStringForm = typeof paramsOrFallback === 'string'; const params = isStringForm ? {} : paramsOrFallback; return (params as Record)['n']; } function tAfter(paramsOrFallback: string | Record = EMPTY_PARAMS): unknown { const isStringForm = typeof paramsOrFallback === 'string'; const params = isStringForm ? EMPTY_PARAMS : paramsOrFallback; return (params as Record)['n']; } describe('round14 §F1 — t() shared empty params', () => { it('produces identical results for the no-param call forms (equivalence gate)', () => { expect(tAfter()).toBe(tBefore()); expect(tAfter('Owner')).toBe(tBefore('Owner')); expect(tAfter({ n: 5 })).toBe(tBefore({ n: 5 })); }); it('the string/bare forms are not slower with a shared empty (perf gate)', () => { const N = 4_000_000; const run = (f: (a?: string | Record) => unknown): number => { let sink: unknown; const t0 = performance.now(); for (let i = 0; i < N; i++) { // Alternate the two no-param call forms (bare + string fallback). sink = i & 1 ? f('Fallback') : f(); } void sink; return performance.now() - t0; }; // Warm-up then measure (best-of-3 to damp GC/JIT noise). run(tBefore); run(tAfter); const beforeMs = Math.min(run(tBefore), run(tBefore), run(tBefore)); const afterMs = Math.min(run(tAfter), run(tAfter), run(tAfter)); console.info( `§F1 ${N} no-param t() calls: fresh {} ${beforeMs.toFixed(1)} ms vs shared frozen ${afterMs.toFixed(1)} ms (${(beforeMs / afterMs).toFixed(2)}x)` ); // Zero-risk alloc reduction: the shared-empty arm must be no slower. expect(afterMs).toBeLessThanOrEqual(beforeMs * 1.05); }); });