From 572c15cb27d428d987c59f57fa69f81ebddfedcc Mon Sep 17 00:00:00 2001 From: chh-ay Date: Mon, 27 Jul 2026 21:35:59 +0700 Subject: [PATCH 1/2] perf(bench): add render diagnostic scenarios --- .changeset/swift-scenes-observe.md | 4 + bench/package.json | 1 + bench/src/render-bench.ts | 166 ++++++++++++++++++++++++++- bench/src/render-driver.ts | 84 +++++++++----- bench/src/render-protocol.ts | 23 +++- bench/src/render-scenarios.ts | 170 +++++++++++++++++++++++++++- bench/test/render-driver.test.ts | 12 +- bench/test/render-scenarios.test.ts | 70 +++++++++++- 8 files changed, 488 insertions(+), 42 deletions(-) create mode 100644 .changeset/swift-scenes-observe.md diff --git a/.changeset/swift-scenes-observe.md b/.changeset/swift-scenes-observe.md new file mode 100644 index 00000000..559e44a7 --- /dev/null +++ b/.changeset/swift-scenes-observe.md @@ -0,0 +1,4 @@ +--- +--- + +Add measured-only render diagnostics for formula, long-text, fractional-DPR, and million-row geometry paths. diff --git a/bench/package.json b/bench/package.json index 77228cfc..c32b558d 100644 --- a/bench/package.json +++ b/bench/package.json @@ -20,6 +20,7 @@ "bench:resource:smoke": "bun run src/resource-bench.ts --smoke", "bench:render:prepare": "cd .. && bun run build:wasm && bun run --filter '@sheetwrite/core' build", "bench:render": "bun run src/render-driver.ts", + "bench:render:diagnostic": "bun run src/render-driver.ts --scenario-set diagnostic --engine sheetwrite", "bench:render:smoke": "bun run src/render-driver.ts --smoke", "bench:render:validate": "bun run src/render-driver.ts --validate results/render-results.json --gate-mode full", "bench:xlsx": "bun run src/xlsx-bench.ts", diff --git a/bench/src/render-bench.ts b/bench/src/render-bench.ts index 906ce53b..921ce85b 100644 --- a/bench/src/render-bench.ts +++ b/bench/src/render-bench.ts @@ -1,6 +1,7 @@ import { type Column, createGrid, + type DocumentOp, type Grid, initSheetwrite, type Workbook, @@ -23,6 +24,7 @@ import "handsontable/styles/ht-theme-main.css"; import { COLUMNS, type ColumnarDataset, datasetChecksum, makeColumnar, toAoA } from "./dataset.js"; import { createHandsontable } from "./handsontable-runtime.js"; import { + ALL_RENDER_SCENARIOS, type BrowserCombinationResult, type EngineId, type FailedScenario, @@ -32,10 +34,13 @@ import { RENDER_SCENARIOS, RENDER_VIEWPORT, type RenderResourceMetrics, + type ScenarioId, type ScenarioResult, } from "./render-protocol.js"; import { type CellSelection, + type GeometryObservation, + measureUnresizedMillionRowGeometry, type RenderBenchAdapter, runRenderScenario, type ScrollObservation, @@ -44,10 +49,14 @@ import { const SHEET = "bench"; const SHEETWRITE_ROW_HEIGHT = 28; const HANDSONTABLE_ROW_HEIGHT = 23; +const FORMULA_DENSE_ROWS = 64; +const TRANSACTION_CHUNK_SIZE = 8_000; +const LONG_TEXT_SUFFIX = "x".repeat(192); interface PageConfiguration { readonly engine: EngineId; readonly rows: number; + readonly scenarios: readonly ScenarioId[]; readonly measuredSamples: number; readonly warmupSamples: number; readonly minimumSampleDurationMs: number; @@ -92,6 +101,13 @@ function makeWorkbook(rowCount: number): Workbook { })); return { activeSheet: SHEET, sheets: [{ id: SHEET, name: "Bench", rowCount, columns }] }; } +function datasetValueAt(dataset: ColumnarDataset, row: number, col: number): string | number { + if (col === 0) return dataset.id[row]!; + if (col === 1) return dataset.date[row]!; + if (col === 2) return dataset.customer[row]!; + if (col === 3) return dataset.city[row]!; + return dataset.amount[row]!; +} class SheetwriteAdapter implements RenderBenchAdapter { readonly id = "sheetwrite" as const; @@ -100,8 +116,11 @@ class SheetwriteAdapter implements RenderBenchAdapter { private grid!: Grid; private host!: HTMLElement; private readonly data: { rowCount: number; columns: Record> }; - + private readonly dataset: ColumnarDataset; + private formulaDenseRows = 0; + private textHeavyRows = 0; constructor(dataset: ColumnarDataset) { + this.dataset = dataset; this.initialRowCount = dataset.rowCount; this.data = { rowCount: dataset.rowCount, @@ -190,6 +209,7 @@ class SheetwriteAdapter implements RenderBenchAdapter { maximumTop: Math.max(0, element.scrollHeight - element.clientHeight), maximumLeft: Math.max(0, element.scrollWidth - element.clientWidth), firstVisibleRow: Math.floor(element.scrollTop / SHEETWRITE_ROW_HEIGHT), + devicePixelRatio: window.devicePixelRatio, }; } @@ -261,6 +281,79 @@ class SheetwriteAdapter implements RenderBenchAdapter { return getNumberFormatResourceStatsForTest(); } + private applyPatches(patches: readonly DocumentOp[]): void { + for (let start = 0; start < patches.length; start += TRANSACTION_CHUNK_SIZE) { + this.grid.store.applyTransaction({ + patches: patches.slice(start, start + TRANSACTION_CHUNK_SIZE), + }); + } + this.grid.refresh(); + } + + installFormulaDense(): void { + this.formulaDenseRows = Math.min(FORMULA_DENSE_ROWS, this.initialRowCount - 1); + const patches: DocumentOp[] = []; + for (let row = 1; row <= this.formulaDenseRows; row++) { + for (let col = 1; col < this.colCount; col++) { + patches.push({ + op: "set", + addr: { sheet: SHEET, row, col }, + value: { kind: "formula", src: `=A${row + 1}+${col}` }, + }); + } + } + this.applyPatches(patches); + } + + clearFormulaDense(): void { + const patches: DocumentOp[] = []; + for (let row = 1; row <= this.formulaDenseRows; row++) { + for (let col = 1; col < this.colCount; col++) { + patches.push({ + op: "set", + addr: { sheet: SHEET, row, col }, + value: { kind: "literal", value: datasetValueAt(this.dataset, row, col) }, + }); + } + } + this.formulaDenseRows = 0; + this.applyPatches(patches); + } + + installTextHeavy(rowCount: number): void { + this.textHeavyRows = Math.min(rowCount, this.initialRowCount - 1); + const patches: DocumentOp[] = []; + for (let row = 1; row <= this.textHeavyRows; row++) { + for (let col = 1; col < this.colCount; col++) { + patches.push({ + op: "set", + addr: { sheet: SHEET, row, col }, + value: { kind: "literal", value: `diagnostic-long-${row}-${col}-${LONG_TEXT_SUFFIX}` }, + }); + } + } + this.applyPatches(patches); + } + + clearTextHeavy(): void { + const patches: DocumentOp[] = []; + for (let row = 1; row <= this.textHeavyRows; row++) { + for (let col = 1; col < this.colCount; col++) { + patches.push({ + op: "set", + addr: { sheet: SHEET, row, col }, + value: { kind: "literal", value: datasetValueAt(this.dataset, row, col) }, + }); + } + } + this.textHeavyRows = 0; + this.applyPatches(patches); + } + + measureUnresizedMillionRowGeometry(): GeometryObservation { + return measureUnresizedMillionRowGeometry(); + } + installMergeHeavy(): void { const count = Math.min(2_000, Math.floor(this.initialRowCount / 2)); this.grid.store.applyTransaction({ @@ -304,8 +397,12 @@ class HandsontableAdapter implements RenderBenchAdapter { private hot!: HotInstance; private host!: HTMLElement; private readonly data: CellValue[][]; + private readonly dataset: ColumnarDataset; + private formulaDenseRows = 0; + private textHeavyRows = 0; constructor(dataset: ColumnarDataset) { + this.dataset = dataset; this.initialRowCount = dataset.rowCount; this.data = toAoA(dataset); } @@ -411,6 +508,7 @@ class HandsontableAdapter implements RenderBenchAdapter { maximumTop: Math.max(0, element.scrollHeight - element.clientHeight), maximumLeft: Math.max(0, element.scrollWidth - element.clientWidth), firstVisibleRow: Math.floor(element.scrollTop / HANDSONTABLE_ROW_HEIGHT), + devicePixelRatio: window.devicePixelRatio, }; } @@ -477,6 +575,50 @@ class HandsontableAdapter implements RenderBenchAdapter { return getNumberFormatResourceStatsForTest(); } + installFormulaDense(): void { + this.formulaDenseRows = Math.min(FORMULA_DENSE_ROWS, this.initialRowCount - 1); + for (let row = 1; row <= this.formulaDenseRows; row++) { + for (let col = 1; col < this.colCount; col++) { + this.data[row]![col] = `=A${row + 1}+${col}`; + } + } + this.hot.render(); + } + + clearFormulaDense(): void { + for (let row = 1; row <= this.formulaDenseRows; row++) { + for (let col = 1; col < this.colCount; col++) { + this.data[row]![col] = datasetValueAt(this.dataset, row, col); + } + } + this.formulaDenseRows = 0; + this.hot.render(); + } + + installTextHeavy(rowCount: number): void { + this.textHeavyRows = Math.min(rowCount, this.initialRowCount - 1); + for (let row = 1; row <= this.textHeavyRows; row++) { + for (let col = 1; col < this.colCount; col++) { + this.data[row]![col] = `diagnostic-long-${row}-${col}-${LONG_TEXT_SUFFIX}`; + } + } + this.hot.render(); + } + + clearTextHeavy(): void { + for (let row = 1; row <= this.textHeavyRows; row++) { + for (let col = 1; col < this.colCount; col++) { + this.data[row]![col] = datasetValueAt(this.dataset, row, col); + } + } + this.textHeavyRows = 0; + this.hot.render(); + } + + measureUnresizedMillionRowGeometry(): GeometryObservation { + return measureUnresizedMillionRowGeometry(); + } + installMergeHeavy(): void { const count = Math.min(2_000, Math.floor(this.initialRowCount / 2)); this.hot.updateSettings({ @@ -598,9 +740,9 @@ async function run(configuration: PageConfiguration): Promise { window.__benchResults = output; const results: ScenarioResult[] = []; - for (const scenario of RENDER_SCENARIOS) { - setStatus(`running ${scenario.id}`); - const result = runRenderScenario(adapter, dataset, scenario.id, { + for (const scenarioId of configuration.scenarios) { + setStatus(`running ${scenarioId}`); + const result = runRenderScenario(adapter, dataset, scenarioId, { runId: configuration.runId, round: configuration.round, warmupSamples: configuration.warmupSamples, @@ -620,7 +762,7 @@ async function run(configuration: PageConfiguration): Promise { // rolled back). Rebuild the fixture so later scenarios validate against // the canonical document instead of cascading the wreckage into // spurious failures. - setStatus(`rebuilding ${configuration.engine} after ${scenario.id} failure`); + setStatus(`rebuilding ${configuration.engine} after ${scenarioId} failure`); try { adapter.destroy(); } catch { @@ -665,8 +807,22 @@ function readConfiguration(params: URLSearchParams): PageConfiguration { const engine = params.get("engine") === "handsontable" ? "handsontable" : "sheetwrite"; const runId = params.get("runId"); if (!runId) throw new Error("render benchmark requires a runId"); + const requestedScenarios = + params.get("scenarios")?.split(",") ?? RENDER_SCENARIOS.map((scenario) => scenario.id); + if ( + requestedScenarios.length === 0 || + new Set(requestedScenarios).size !== requestedScenarios.length + ) { + throw new Error("render benchmark scenarios must be non-empty and unique"); + } + for (const scenarioId of requestedScenarios) { + if (!ALL_RENDER_SCENARIOS.some((scenario) => scenario.id === scenarioId)) { + throw new Error(`unknown render benchmark scenario: ${scenarioId}`); + } + } return { engine, + scenarios: requestedScenarios as ScenarioId[], rows: positiveInteger(params.get("rows"), 100_000), measuredSamples: positiveInteger(params.get("samples"), 3), warmupSamples: nonNegativeInteger(params.get("warmups"), 1), diff --git a/bench/src/render-driver.ts b/bench/src/render-driver.ts index a5a8432f..2d2de345 100644 --- a/bench/src/render-driver.ts +++ b/bench/src/render-driver.ts @@ -9,6 +9,7 @@ import { validateRenderGateArtifact } from "./render-gate.js"; import { type BrowserCombinationResult, type BrowserLaunchAttempt, + DIAGNOSTIC_RENDER_SCENARIOS, ENGINE_IDS, type EngineId, type FailedScenario, @@ -24,6 +25,7 @@ import { type RenderBenchmarkArtifact, type RenderRunConfig, renderBenchmarkMarkdown, + type ScenarioId, type ScenarioResult, scenarioGroup, summarizeCompleteness, @@ -34,11 +36,15 @@ const BENCH_ROOT = fileURLToPath(new URL("..", import.meta.url)); const REPOSITORY_ROOT = resolve(BENCH_ROOT, ".."); const DEFAULT_JSON_PATH = resolve(BENCH_ROOT, "results/render-results.json"); const DEFAULT_MARKDOWN_PATH = resolve(BENCH_ROOT, "results/render-results.md"); +const DIAGNOSTIC_JSON_PATH = resolve(BENCH_ROOT, "results/render-diagnostics.json"); +const DIAGNOSTIC_MARKDOWN_PATH = resolve(BENCH_ROOT, "results/render-diagnostics.md"); interface DriverConfiguration { readonly smoke: boolean; readonly engines: readonly EngineId[]; readonly rows: readonly number[]; + readonly scenarios: readonly ScenarioId[]; + readonly diagnostic: boolean; readonly rounds: number; readonly measuredSamples: number; readonly warmupSamples: number; @@ -55,6 +61,7 @@ interface CombinationConfiguration { readonly round: number; readonly engine: EngineId; readonly rows: number; + readonly scenarios?: readonly ScenarioId[]; readonly measuredSamples: number; readonly warmupSamples: number; readonly minimumSampleDurationMs: number; @@ -105,10 +112,20 @@ function nonNegativeIntegerArgument( function parseConfiguration(args: readonly string[]): DriverConfiguration { const smoke = args.includes("--smoke"); + const scenarioSet = argumentValue(args, "--scenario-set") ?? "gate"; + if (scenarioSet !== "gate" && scenarioSet !== "diagnostic") { + throw new TypeError("--scenario-set must be gate or diagnostic"); + } + const diagnostic = scenarioSet === "diagnostic"; + const scenarios = diagnostic + ? DIAGNOSTIC_RENDER_SCENARIOS.map((scenario) => scenario.id) + : RENDER_SCENARIOS.map((scenario) => scenario.id); const engineArg = argumentValue(args, "--engine"); const engines: readonly EngineId[] = engineArg === undefined - ? ENGINE_IDS + ? diagnostic + ? ["sheetwrite"] + : ENGINE_IDS : [ ENGINE_IDS.includes(engineArg as EngineId) ? (engineArg as EngineId) @@ -116,6 +133,9 @@ function parseConfiguration(args: readonly string[]): DriverConfiguration { throw new TypeError(`--engine must be one of: ${ENGINE_IDS.join(", ")}`); })(), ]; + if (diagnostic && engines.some((engine) => engine !== "sheetwrite")) { + throw new TypeError("the diagnostic scenario set currently supports only sheetwrite"); + } const rowsArg = argumentValue(args, "--rows"); const rows = rowsArg ? rowsArg.split(",").map((entry) => { @@ -131,18 +151,23 @@ function parseConfiguration(args: readonly string[]): DriverConfiguration { const compactSamples = args.includes("--compact-samples"); const outputArg = argumentValue(args, "--output"); const markdownArg = argumentValue(args, "--markdown-output"); - const outputPath = outputArg ?? (smoke ? undefined : DEFAULT_JSON_PATH); + const outputPath = + outputArg ?? (smoke ? undefined : diagnostic ? DIAGNOSTIC_JSON_PATH : DEFAULT_JSON_PATH); const markdownPath = markdownArg ?? (outputPath === undefined ? undefined : outputArg === undefined - ? DEFAULT_MARKDOWN_PATH + ? diagnostic + ? DIAGNOSTIC_MARKDOWN_PATH + : DEFAULT_MARKDOWN_PATH : outputPath.replace(/\.json$/u, ".md")); return { smoke, engines, rows, + scenarios, + diagnostic, rounds: positiveIntegerArgument(args, "--rounds", smoke ? 1 : 2), measuredSamples: positiveIntegerArgument(args, "--samples", smoke ? 1 : 3), warmupSamples: nonNegativeIntegerArgument(args, "--warmups", 1), @@ -164,7 +189,7 @@ function normalizedError(error: unknown): Error { function failureForScenario( configuration: CombinationConfiguration, - scenarioId: (typeof RENDER_SCENARIOS)[number]["id"], + scenarioId: ScenarioId, stage: FailureStage, error: unknown, diagnostics: RuntimeDiagnostics, @@ -194,6 +219,10 @@ function failureForScenario( }; } +function combinationScenarios(configuration: CombinationConfiguration): readonly ScenarioId[] { + return configuration.scenarios ?? RENDER_SCENARIOS.map((scenario) => scenario.id); +} + export function createCombinationFailures( configuration: CombinationConfiguration, stage: FailureStage, @@ -202,8 +231,8 @@ export function createCombinationFailures( timeout = false, crash = false, ): FailedScenario[] { - return RENDER_SCENARIOS.map((scenario) => - failureForScenario(configuration, scenario.id, stage, error, diagnostics, timeout, crash), + return combinationScenarios(configuration).map((scenarioId) => + failureForScenario(configuration, scenarioId, stage, error, diagnostics, timeout, crash), ); } @@ -295,20 +324,20 @@ function normalizeBrowserOutput( } const normalized: ScenarioResult[] = []; - for (const scenario of RENDER_SCENARIOS) { + for (const scenarioId of combinationScenarios(configuration)) { const matches = input.results.filter( (entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && "scenarioId" in entry && - entry.scenarioId === scenario.id, + entry.scenarioId === scenarioId, ); if (matches.length !== 1) { normalized.push( failureForScenario( configuration, - scenario.id, + scenarioId, "validate", new Error( matches.length === 0 @@ -338,15 +367,7 @@ function normalizeBrowserOutput( ); } catch (error) { normalized.push( - failureForScenario( - configuration, - scenario.id, - "validate", - error, - diagnostics, - false, - false, - ), + failureForScenario(configuration, scenarioId, "validate", error, diagnostics, false, false), ); } } @@ -420,6 +441,11 @@ async function runCombination( if (!browser) throw new Error("browser launch attempts completed without a browser"); page = await browser.newPage({ viewport: { width: RENDER_VIEWPORT.width + 480, height: RENDER_VIEWPORT.height + 180 }, + deviceScaleFactor: combinationScenarios(configuration).includes( + "scroll-fractional.same-window", + ) + ? 1.25 + : 1, }); page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); @@ -446,6 +472,7 @@ async function runCombination( url.searchParams.set("round", String(configuration.round)); url.searchParams.set("engine", configuration.engine); url.searchParams.set("rows", String(configuration.rows)); + url.searchParams.set("scenarios", combinationScenarios(configuration).join(",")); url.searchParams.set("samples", String(configuration.measuredSamples)); url.searchParams.set("warmups", String(configuration.warmupSamples)); url.searchParams.set("minimumSampleMs", String(configuration.minimumSampleDurationMs)); @@ -469,12 +496,12 @@ async function runCombination( if (state.result) { const partial = normalizeBrowserOutput(state.result, configuration, diagnostics); const byScenario = new Map(partial.map((result) => [result.scenarioId, result])); - const completed = RENDER_SCENARIOS.map((scenario) => { - const result = byScenario.get(scenario.id); + const completed = combinationScenarios(configuration).map((scenarioId) => { + const result = byScenario.get(scenarioId); if (result && result.status === "success") return result; return failureForScenario( configuration, - scenario.id, + scenarioId, observedStage, state.error ? new Error(state.error) : normalized, diagnostics, @@ -613,6 +640,7 @@ async function runDriver(args: readonly string[]): Promise { round, engine, rows, + scenarios: configuration.scenarios, measuredSamples: configuration.measuredSamples, warmupSamples: configuration.warmupSamples, minimumSampleDurationMs: configuration.minimumSampleDurationMs, @@ -633,7 +661,7 @@ async function runDriver(args: readonly string[]): Promise { const config: RenderRunConfig = { engines: configuration.engines, rows: configuration.rows, - scenarios: RENDER_SCENARIOS.map((scenario) => scenario.id), + scenarios: configuration.scenarios, }; const timestamp = new Date().toISOString(); const artifact: RenderBenchmarkArtifact = { @@ -667,11 +695,13 @@ async function runDriver(args: readonly string[]): Promise { config, results, completeness: summarizeCompleteness(config, configuration.rounds, results), - reproductionCommands: [ - "bun run --filter '@sheetwrite/bench' bench:render", - "bun run --filter '@sheetwrite/bench' bench:render:smoke -- --engine sheetwrite", - "bun run --filter '@sheetwrite/bench' bench:render:smoke -- --engine handsontable", - ], + reproductionCommands: configuration.diagnostic + ? ["bun run --filter '@sheetwrite/bench' bench:render:diagnostic"] + : [ + "bun run --filter '@sheetwrite/bench' bench:render", + "bun run --filter '@sheetwrite/bench' bench:render:smoke -- --engine sheetwrite", + "bun run --filter '@sheetwrite/bench' bench:render:smoke -- --engine handsontable", + ], }; const json = stableJson(artifact); const parsed = parseRenderArtifactJson(json, { expectedRunId: runId }); diff --git a/bench/src/render-protocol.ts b/bench/src/render-protocol.ts index a83e7139..b03adfeb 100644 --- a/bench/src/render-protocol.ts +++ b/bench/src/render-protocol.ts @@ -16,7 +16,9 @@ export type ScenarioGroup = | "altering" | "arrow-keys-navigation" | "formatting" - | "merges"; + | "merges" + | "formulae" + | "geometry"; export const RENDER_SCENARIOS = [ { id: "scroll-down.top-left", group: "view-scrolling" }, @@ -35,7 +37,16 @@ export const RENDER_SCENARIOS = [ { id: "merge-heavy.paint", group: "merges" }, ] as const satisfies readonly { readonly id: string; readonly group: ScenarioGroup }[]; -export type ScenarioId = (typeof RENDER_SCENARIOS)[number]["id"]; +export const DIAGNOSTIC_RENDER_SCENARIOS = [ + { id: "formula-dense.paint", group: "formulae" }, + { id: "text-heavy.long-scroll", group: "view-scrolling" }, + { id: "scroll-fractional.same-window", group: "view-scrolling" }, + { id: "geometry-unresized.1m", group: "geometry" }, +] as const satisfies readonly { readonly id: string; readonly group: ScenarioGroup }[]; + +export const ALL_RENDER_SCENARIOS = [...RENDER_SCENARIOS, ...DIAGNOSTIC_RENDER_SCENARIOS] as const; + +export type ScenarioId = (typeof ALL_RENDER_SCENARIOS)[number]["id"]; export type FailureStage = | "build" | "launch" @@ -185,7 +196,7 @@ export interface BrowserCombinationResult { } const SCENARIO_GROUPS = new Map( - RENDER_SCENARIOS.map((scenario) => [scenario.id, scenario.group]), + ALL_RENDER_SCENARIOS.map((scenario) => [scenario.id, scenario.group]), ); export function scenarioGroup(scenarioId: ScenarioId): ScenarioGroup { @@ -386,12 +397,12 @@ function parseSample(value: unknown, path: string): MeasuredSample { function parseIdentity(value: Record, path: string): ScenarioIdentity { const scenarioId = enumValue( value.scenarioId, - RENDER_SCENARIOS.map((scenario) => scenario.id), + ALL_RENDER_SCENARIOS.map((scenario) => scenario.id), `${path}.scenarioId`, ); const group = enumValue( value.group, - ["view-scrolling", "editing", "altering", "arrow-keys-navigation", "formatting", "merges"], + ALL_RENDER_SCENARIOS.map((scenario) => scenario.group), `${path}.group`, ); if (group !== scenarioGroup(scenarioId)) { @@ -585,7 +596,7 @@ function parseConfig(value: unknown, path: string): RenderRunConfig { const scenarios = array(input.scenarios, `${path}.scenarios`).map((scenario, index) => enumValue( scenario, - RENDER_SCENARIOS.map((entry) => entry.id), + ALL_RENDER_SCENARIOS.map((entry) => entry.id), `${path}.scenarios[${index}]`, ), ); diff --git a/bench/src/render-scenarios.ts b/bench/src/render-scenarios.ts index e7afbb03..b74d0b37 100644 --- a/bench/src/render-scenarios.ts +++ b/bench/src/render-scenarios.ts @@ -1,11 +1,12 @@ +import { OffsetIndex } from "../../packages/core/src/fenwick.js"; import type { ColumnarDataset } from "./dataset.js"; import { logicalValueChecksum } from "./dataset.js"; import { + ALL_RENDER_SCENARIOS, type FailedScenario, type MeasuredSample, type MemoryDelta, type MergeIndexResourceMetrics, - RENDER_SCENARIOS, type RenderResourceMetrics, type ScenarioId, type ScenarioIdentity, @@ -18,6 +19,10 @@ import { summarizeFinite } from "./stats.js"; const SCROLL_STEP = 50; const EDIT_VALUE = "Benchmark edit"; const ALTER_COUNT = 5; +const FRACTIONAL_SCROLL_STEP = 1; +const LONG_SCROLL_STEP_PX = 448; +const LONG_SCROLL_ROW_STRIDE = 16; +const LONG_SCROLL_MAX_STEPS = 1_100; export interface CellSelection { readonly row: number; @@ -30,6 +35,28 @@ export interface ScrollObservation { readonly maximumTop: number; readonly maximumLeft: number; readonly firstVisibleRow: number; + readonly devicePixelRatio: number; +} +export interface GeometryObservation { + readonly count: number; + readonly totalHeight: number; + readonly middleRow: number; + readonly middleTop: number; + readonly lastRow: number; + readonly lastTop: number; +} +export function measureUnresizedMillionRowGeometry(): GeometryObservation { + const index = new OffsetIndex(1_000_000, 28); + const middle = index.rowAtOffset(14_000_005); + const last = index.rowAtOffset(index.totalHeight - 1); + return { + count: index.count, + totalHeight: index.totalHeight, + middleRow: middle.row, + middleTop: middle.top, + lastRow: last.row, + lastTop: last.top, + }; } /** Repository-owned structural surface shared by both browser engines. */ @@ -58,6 +85,11 @@ export interface RenderBenchAdapter { repaint(): void; formattedSentinels(): readonly [string, string]; formatResources(): RenderResourceMetrics; + installFormulaDense(): void; + clearFormulaDense(): void; + installTextHeavy(rowCount: number): void; + clearTextHeavy(): void; + measureUnresizedMillionRowGeometry(): GeometryObservation; installMergeHeavy(): void; clearMergeHeavy(): void; resetMergeResources(): void; @@ -79,6 +111,7 @@ export class ScenarioValidationError extends Error { } interface ScenarioActions { + readonly setup?: () => void; readonly prepare: () => void; readonly action: () => void; readonly cleanup: () => void; @@ -210,6 +243,138 @@ function scenarioActions( const lastRow = dataset.rowCount - 1; const lastCol = adapter.colCount - 1; + if (scenarioId === "formula-dense.paint") { + const prepare = (): void => adapter.prepareScroll("top", false); + const action = (): void => adapter.repaint(); + return { + setup: () => adapter.installFormulaDense(), + prepare, + action, + cleanup: () => {}, + validateEffect: (observations) => { + try { + prepare(); + action(); + checkpoint( + observations, + "formula-dense.paint resolves first visible formula", + Number(dataset.id[1]) + 1, + adapter.cellValue(1, 1), + ); + checkpoint( + observations, + "formula-dense.paint resolves last visible formula column", + Number(dataset.id[1]) + 4, + adapter.cellValue(1, 4), + ); + } finally { + adapter.clearFormulaDense(); + } + }, + }; + } + + if (scenarioId === "text-heavy.long-scroll") { + const steps = Math.min( + LONG_SCROLL_MAX_STEPS, + Math.max(1, Math.floor(dataset.rowCount / LONG_SCROLL_ROW_STRIDE) - 1), + ); + const installedRows = Math.min(dataset.rowCount - 1, steps * LONG_SCROLL_ROW_STRIDE + 64); + const prepare = (): void => adapter.prepareScroll("top", false); + const action = (): void => { + for (let step = 0; step < steps; step++) { + adapter.scrollBy("top", LONG_SCROLL_STEP_PX); + } + }; + return { + setup: () => adapter.installTextHeavy(installedRows), + prepare, + action, + cleanup: () => {}, + validateEffect: (observations) => { + try { + prepare(); + const before = adapter.scrollObservation(); + action(); + const after = adapter.scrollObservation(); + checkpoint( + observations, + "text-heavy.long-scroll advances through multiple row windows", + true, + after.firstVisibleRow > before.firstVisibleRow, + ); + const observedRow = Math.min(installedRows, Math.max(1, after.firstVisibleRow)); + checkpoint( + observations, + "text-heavy.long-scroll retains unique long text", + true, + String(adapter.cellValue(observedRow, 2)).startsWith("diagnostic-long-"), + ); + } finally { + adapter.clearTextHeavy(); + } + }, + }; + } + + if (scenarioId === "scroll-fractional.same-window") { + const prepare = (): void => adapter.prepareScroll("top", false); + const action = (): void => adapter.scrollBy("top", FRACTIONAL_SCROLL_STEP); + return { + prepare, + action, + cleanup: () => {}, + validateEffect: (observations) => { + prepare(); + const before = adapter.scrollObservation(); + action(); + const after = adapter.scrollObservation(); + const deviceDelta = (after.top - before.top) * after.devicePixelRatio; + checkpoint( + observations, + "scroll-fractional.same-window uses a fractional device-pixel delta", + true, + after.top === Math.min(before.maximumTop, before.top + FRACTIONAL_SCROLL_STEP) && + !Number.isInteger(deviceDelta), + ); + checkpoint( + observations, + "scroll-fractional.same-window keeps the logical row window", + before.firstVisibleRow, + after.firstVisibleRow, + ); + }, + }; + } + + if (scenarioId === "geometry-unresized.1m") { + let observed: GeometryObservation | undefined; + const action = (): void => { + observed = adapter.measureUnresizedMillionRowGeometry(); + }; + return { + prepare: () => {}, + action, + cleanup: () => {}, + validateEffect: (observations) => { + action(); + checkpoint( + observations, + "geometry-unresized.1m builds exact uniform geometry", + JSON.stringify({ + count: 1_000_000, + totalHeight: 28_000_000, + middleRow: 500_000, + middleTop: 14_000_000, + lastRow: 999_999, + lastTop: 27_999_972, + }), + JSON.stringify(observed), + ); + }, + }; + } + if (scenarioId === "formatted-paint.top-left") { const originalDate = dataset.date[0]!; const originalAmount = dataset.amount[0]!; @@ -599,7 +764,7 @@ export function runRenderScenario( scenarioId: ScenarioId, options: ScenarioRunOptions, ): ScenarioResult { - if (!RENDER_SCENARIOS.some((scenario) => scenario.id === scenarioId)) { + if (!ALL_RENDER_SCENARIOS.some((scenario) => scenario.id === scenarioId)) { throw new RangeError(`unknown render scenario: ${scenarioId}`); } const identity: ScenarioIdentity = { @@ -619,6 +784,7 @@ export function runRenderScenario( try { options.onStage?.("validate"); validateCanonicalState(adapter, dataset, validation); + actions.setup?.(); stage = "warmup"; options.onStage?.("warmup"); for (let index = 0; index < options.warmupSamples; index++) { diff --git a/bench/test/render-driver.test.ts b/bench/test/render-driver.test.ts index c34af829..f5862e8a 100644 --- a/bench/test/render-driver.test.ts +++ b/bench/test/render-driver.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createCombinationFailures } from "../src/render-driver.js"; -import { RENDER_SCENARIOS } from "../src/render-protocol.js"; +import { DIAGNOSTIC_RENDER_SCENARIOS, RENDER_SCENARIOS } from "../src/render-protocol.js"; const configuration = { runId: "launch-fixture", @@ -28,6 +28,16 @@ describe("isolated browser failure envelopes", () => { expect(new Set(results.map((result) => result.scenarioId)).size).toBe(RENDER_SCENARIOS.length); }); + test("failure output keeps the requested diagnostic scenario list", () => { + const scenarios = DIAGNOSTIC_RENDER_SCENARIOS.map((scenario) => scenario.id); + const results = createCombinationFailures( + { ...configuration, scenarios }, + "launch", + new Error("browser executable does not exist"), + ); + expect(results.map((result) => result.scenarioId)).toEqual(scenarios); + }); + test("timeouts retain timeout/crash diagnostics in every cell", () => { const results = createCombinationFailures( configuration, diff --git a/bench/test/render-scenarios.test.ts b/bench/test/render-scenarios.test.ts index 476e5095..a349da4d 100644 --- a/bench/test/render-scenarios.test.ts +++ b/bench/test/render-scenarios.test.ts @@ -1,7 +1,9 @@ import { describe, expect, test } from "bun:test"; import { makeColumnar, toAoA } from "../src/dataset.js"; +import { DIAGNOSTIC_RENDER_SCENARIOS } from "../src/render-protocol.js"; import { type CellSelection, + measureUnresizedMillionRowGeometry, type RenderBenchAdapter, runRenderScenario, type ScrollObservation, @@ -18,6 +20,7 @@ class FakeAdapter implements RenderBenchAdapter { readonly initialRowCount: number; readonly colCount = 5; readonly values: unknown[][]; + private readonly originalValues: unknown[][]; corruptNavigation = false; private mounted = true; private selected: CellSelection | null = null; @@ -29,7 +32,8 @@ class FakeAdapter implements RenderBenchAdapter { constructor(rows = 200, evidence: FakeEvidence = {}) { const dataset = makeColumnar(rows); this.initialRowCount = rows; - this.values = toAoA(dataset); + this.originalValues = toAoA(dataset); + this.values = this.originalValues.map((row) => [...row]); this.evidence = evidence; } @@ -80,6 +84,7 @@ class FakeAdapter implements RenderBenchAdapter { maximumTop: 1_000, maximumLeft: 500, firstVisibleRow: Math.floor(this.top / 25), + devicePixelRatio: 1.25, }; } @@ -138,6 +143,49 @@ class FakeAdapter implements RenderBenchAdapter { }; } + installFormulaDense(): void { + for (let row = 1; row <= Math.min(64, this.initialRowCount - 1); row++) { + for (let col = 1; col < this.colCount; col++) { + this.values[row]![col] = Number(this.values[row]![0]) + col; + } + } + } + + clearFormulaDense(): void { + for (let row = 1; row <= Math.min(64, this.initialRowCount - 1); row++) { + for (let col = 1; col < this.colCount; col++) { + this.values[row]![col] = this.originalValues[row]![col]; + } + } + } + + installTextHeavy(rowCount: number): void { + for (let row = 1; row <= Math.min(rowCount, this.initialRowCount - 1); row++) { + for (let col = 1; col < this.colCount; col++) { + this.values[row]![col] = `diagnostic-long-${row}-${col}`; + } + } + } + + clearTextHeavy(): void { + for (let row = 1; row < this.initialRowCount; row++) { + for (let col = 1; col < this.colCount; col++) { + this.values[row]![col] = this.originalValues[row]![col]; + } + } + } + + measureUnresizedMillionRowGeometry() { + return { + count: 1_000_000, + totalHeight: 28_000_000, + middleRow: 500_000, + middleTop: 14_000_000, + lastRow: 999_999, + lastTop: 27_999_972, + }; + } + installMergeHeavy(): void {} clearMergeHeavy(): void {} @@ -221,6 +269,26 @@ describe("scenario correctness checkpoints", () => { ); }); + test("measures real million-row uniform OffsetIndex geometry", () => { + expect(measureUnresizedMillionRowGeometry()).toEqual({ + count: 1_000_000, + totalHeight: 28_000_000, + middleRow: 500_000, + middleTop: 14_000_000, + lastRow: 999_999, + lastTop: 27_999_972, + }); + }); + + test("records each measured-only diagnostic scenario without promoting it to the gate", () => { + for (const scenario of DIAGNOSTIC_RENDER_SCENARIOS) { + const result = runRenderScenario(new FakeAdapter(), dataset, scenario.id, options); + expect(result.status).toBe("success"); + if (result.status !== "success") throw new Error(result.message); + expect(result.validation.every((observation) => observation.passed)).toBe(true); + } + }); + test("rejects corrupt formatter and merge-index evidence", () => { for (const [adapter, scenario, checkpoint] of [ [ From c6dfffec23da106f5746f533073be6247e026b40 Mon Sep 17 00:00:00 2001 From: chh-ay Date: Mon, 27 Jul 2026 21:41:36 +0700 Subject: [PATCH 2/2] perf(bench): record render diagnostic repeats --- bench/results/render-diagnostics.json | 1309 +++++++++++++++++++++++++ bench/results/render-diagnostics.md | 47 + 2 files changed, 1356 insertions(+) create mode 100644 bench/results/render-diagnostics.json create mode 100644 bench/results/render-diagnostics.md diff --git a/bench/results/render-diagnostics.json b/bench/results/render-diagnostics.json new file mode 100644 index 00000000..4ee9a825 --- /dev/null +++ b/bench/results/render-diagnostics.json @@ -0,0 +1,1309 @@ +{ + "protocolVersion": 1, + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "metadata": { + "commit": "572c15cb27d428d987c59f57fa69f81ebddfedcc", + "dirty": false, + "timestamp": "2026-07-27T14:41:01.920Z", + "bunVersion": "1.3.14", + "nodeVersion": "24.3.0", + "browserVersion": "149.0.7827.55", + "os": "linux 7.1.3-2-cachyos", + "arch": "x64", + "cpu": "12th Gen Intel(R) Core(TM) i9-12900H", + "engineVersions": { + "sheetwrite": "0.3.1", + "handsontable": "18.0.0" + }, + "datasetSeed": 1592639710, + "datasetHashes": { + "100000": "fnv1a32:178eac66" + }, + "viewport": { + "width": 640, + "height": 480 + }, + "measuredSamples": 3, + "warmupSamples": 1, + "minimumSampleDurationMs": 100, + "rounds": 3, + "orderSeed": 1371602926, + "engineOrder": [ + [ + "sheetwrite" + ], + [ + "sheetwrite" + ], + [ + "sheetwrite" + ] + ], + "launchAttempts": [ + { + "round": 1, + "engine": "sheetwrite", + "rows": 100000, + "attempt": 1, + "success": true, + "errorClass": null, + "message": null + }, + { + "round": 2, + "engine": "sheetwrite", + "rows": 100000, + "attempt": 1, + "success": true, + "errorClass": null, + "message": null + }, + { + "round": 3, + "engine": "sheetwrite", + "rows": 100000, + "attempt": 1, + "success": true, + "errorClass": null, + "message": null + } + ] + }, + "config": { + "engines": [ + "sheetwrite" + ], + "rows": [ + 100000 + ], + "scenarios": [ + "formula-dense.paint", + "text-heavy.long-scroll", + "scroll-fractional.same-window", + "geometry-unresized.1m" + ] + }, + "results": [ + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 1, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "formula-dense.paint", + "group": "formulae", + "status": "success", + "operationCount": 773, + "rawSamples": [ + { + "index": 0, + "durationMs": 105.3999999947846, + "operationCount": 205, + "perOperationMs": 0.5141463414379737 + }, + { + "index": 1, + "durationMs": 128.90000000223517, + "operationCount": 284, + "perOperationMs": 0.45387323944449004 + }, + { + "index": 2, + "durationMs": 118.90000000223517, + "operationCount": 284, + "perOperationMs": 0.41866197183885623 + } + ], + "medianMs": 0.45387323944449004, + "p95Ms": 0.5081190312386253, + "madMs": 0.03521126760563381, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves first visible formula", + "expected": "3", + "observed": "3", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves last visible formula column", + "expected": "6", + "observed": "6", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 20159169, + "afterBytes": 25154572, + "deltaBytes": 4995403 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 1, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "text-heavy.long-scroll", + "group": "view-scrolling", + "status": "success", + "operationCount": 3, + "rawSamples": [ + { + "index": 0, + "durationMs": 4014.5999999996275, + "operationCount": 1, + "perOperationMs": 4014.5999999996275 + }, + { + "index": 1, + "durationMs": 3920.800000000745, + "operationCount": 1, + "perOperationMs": 3920.800000000745 + }, + { + "index": 2, + "durationMs": 4057.5999999996275, + "operationCount": 1, + "perOperationMs": 4057.5999999996275 + } + ], + "medianMs": 4014.5999999996275, + "p95Ms": 4053.2999999996273, + "madMs": 43, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll advances through multiple row windows", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll retains unique long text", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 25184892, + "afterBytes": 54231561, + "deltaBytes": 29046669 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 1, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "scroll-fractional.same-window", + "group": "view-scrolling", + "status": "success", + "operationCount": 450, + "rawSamples": [ + { + "index": 0, + "durationMs": 100.49999998696148, + "operationCount": 156, + "perOperationMs": 0.644230769147189 + }, + { + "index": 1, + "durationMs": 100.40000002086163, + "operationCount": 155, + "perOperationMs": 0.647741935618462 + }, + { + "index": 2, + "durationMs": 100.1999999973923, + "operationCount": 139, + "perOperationMs": 0.7208633093337575 + } + ], + "medianMs": 0.647741935618462, + "p95Ms": 0.713551171962228, + "madMs": 0.0035111664712730306, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window uses a fractional device-pixel delta", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window keeps the logical row window", + "expected": "0", + "observed": "0", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 54265593, + "afterBytes": 62823406, + "deltaBytes": 8557813 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 1, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "geometry-unresized.1m", + "group": "geometry", + "status": "success", + "operationCount": 46, + "rawSamples": [ + { + "index": 0, + "durationMs": 103.5, + "operationCount": 15, + "perOperationMs": 6.9 + }, + { + "index": 1, + "durationMs": 101.70000000111759, + "operationCount": 14, + "perOperationMs": 7.264285714365542 + }, + { + "index": 2, + "durationMs": 103.5, + "operationCount": 17, + "perOperationMs": 6.088235294117647 + } + ], + "medianMs": 6.9, + "p95Ms": 7.227857142928988, + "madMs": 0.3642857143655416, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "geometry-unresized.1m builds exact uniform geometry", + "expected": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 62832054, + "afterBytes": 107770594, + "deltaBytes": 44938540 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 2, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "formula-dense.paint", + "group": "formulae", + "status": "success", + "operationCount": 852, + "rawSamples": [ + { + "index": 0, + "durationMs": 118.50000000186265, + "operationCount": 284, + "perOperationMs": 0.41725352113331915 + }, + { + "index": 1, + "durationMs": 116.59999999776483, + "operationCount": 284, + "perOperationMs": 0.41056338027381983 + }, + { + "index": 2, + "durationMs": 108.50000000372529, + "operationCount": 284, + "perOperationMs": 0.382042253534244 + } + ], + "medianMs": 0.41056338027381983, + "p95Ms": 0.4165845070473692, + "madMs": 0.006690140859499316, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves first visible formula", + "expected": "3", + "observed": "3", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves last visible formula column", + "expected": "6", + "observed": "6", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 19939713, + "afterBytes": 29592592, + "deltaBytes": 9652879 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 2, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "text-heavy.long-scroll", + "group": "view-scrolling", + "status": "success", + "operationCount": 3, + "rawSamples": [ + { + "index": 0, + "durationMs": 4226, + "operationCount": 1, + "perOperationMs": 4226 + }, + { + "index": 1, + "durationMs": 3964.4000000003725, + "operationCount": 1, + "perOperationMs": 3964.4000000003725 + }, + { + "index": 2, + "durationMs": 4126.799999998882, + "operationCount": 1, + "perOperationMs": 4126.799999998882 + } + ], + "medianMs": 4126.799999998882, + "p95Ms": 4216.079999999888, + "madMs": 99.20000000111759, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll advances through multiple row windows", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll retains unique long text", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 20251253, + "afterBytes": 57249885, + "deltaBytes": 36998632 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 2, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "scroll-fractional.same-window", + "group": "view-scrolling", + "status": "success", + "operationCount": 476, + "rawSamples": [ + { + "index": 0, + "durationMs": 100.50000000558794, + "operationCount": 166, + "perOperationMs": 0.6054216867806502 + }, + { + "index": 1, + "durationMs": 100.10000000707805, + "operationCount": 156, + "perOperationMs": 0.6416666667120388 + }, + { + "index": 2, + "durationMs": 100.59999999217689, + "operationCount": 154, + "perOperationMs": 0.6532467531959538 + } + ], + "medianMs": 0.6416666667120388, + "p95Ms": 0.6520887445475623, + "madMs": 0.011580086483915064, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window uses a fractional device-pixel delta", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window keeps the logical row window", + "expected": "0", + "observed": "0", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 57283917, + "afterBytes": 66147714, + "deltaBytes": 8863797 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 2, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "geometry-unresized.1m", + "group": "geometry", + "status": "success", + "operationCount": 44, + "rawSamples": [ + { + "index": 0, + "durationMs": 104.09999999962747, + "operationCount": 12, + "perOperationMs": 8.674999999968955 + }, + { + "index": 1, + "durationMs": 102, + "operationCount": 17, + "perOperationMs": 6 + }, + { + "index": 2, + "durationMs": 104.80000000074506, + "operationCount": 15, + "perOperationMs": 6.986666666716337 + } + ], + "medianMs": 6.986666666716337, + "p95Ms": 8.506166666643693, + "madMs": 0.9866666667163368, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "geometry-unresized.1m builds exact uniform geometry", + "expected": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 66156374, + "afterBytes": 291152202, + "deltaBytes": 224995828 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 3, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "formula-dense.paint", + "group": "formulae", + "status": "success", + "operationCount": 711, + "rawSamples": [ + { + "index": 0, + "durationMs": 100.09999999776483, + "operationCount": 225, + "perOperationMs": 0.4448888888789548 + }, + { + "index": 1, + "durationMs": 100.29999999701977, + "operationCount": 202, + "perOperationMs": 0.4965346534505929 + }, + { + "index": 2, + "durationMs": 131.59999999776483, + "operationCount": 284, + "perOperationMs": 0.4633802816822705 + } + ], + "medianMs": 0.4633802816822705, + "p95Ms": 0.49321921627376064, + "madMs": 0.018491392803315743, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves first visible formula", + "expected": "3", + "observed": "3", + "passed": true + }, + { + "checkpoint": "formula-dense.paint resolves last visible formula column", + "expected": "6", + "observed": "6", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 19738833, + "afterBytes": 24694324, + "deltaBytes": 4955491 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 3, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "text-heavy.long-scroll", + "group": "view-scrolling", + "status": "success", + "operationCount": 3, + "rawSamples": [ + { + "index": 0, + "durationMs": 3857.5, + "operationCount": 1, + "perOperationMs": 3857.5 + }, + { + "index": 1, + "durationMs": 3959.699999999255, + "operationCount": 1, + "perOperationMs": 3959.699999999255 + }, + { + "index": 2, + "durationMs": 4288.300000000745, + "operationCount": 1, + "perOperationMs": 4288.300000000745 + } + ], + "medianMs": 3959.699999999255, + "p95Ms": 4255.440000000596, + "madMs": 102.19999999925494, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll advances through multiple row windows", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "text-heavy.long-scroll retains unique long text", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 20257673, + "afterBytes": 56376837, + "deltaBytes": 36119164 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 3, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "scroll-fractional.same-window", + "group": "view-scrolling", + "status": "success", + "operationCount": 371, + "rawSamples": [ + { + "index": 0, + "durationMs": 100.300000006333, + "operationCount": 145, + "perOperationMs": 0.6917241379747103 + }, + { + "index": 1, + "durationMs": 100.59999999590218, + "operationCount": 125, + "perOperationMs": 0.8047999999672174 + }, + { + "index": 2, + "durationMs": 100.49999999627471, + "operationCount": 101, + "perOperationMs": 0.995049504913611 + } + ], + "medianMs": 0.8047999999672174, + "p95Ms": 0.9760245544189716, + "madMs": 0.11307586199250708, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window uses a fractional device-pixel delta", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "scroll-fractional.same-window keeps the logical row window", + "expected": "0", + "observed": "0", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 56410885, + "afterBytes": 63595066, + "deltaBytes": 7184181 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + }, + { + "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "round": 3, + "engine": "sheetwrite", + "rows": 100000, + "scenarioId": "geometry-unresized.1m", + "group": "geometry", + "status": "success", + "operationCount": 44, + "rawSamples": [ + { + "index": 0, + "durationMs": 101.40000000037253, + "operationCount": 13, + "perOperationMs": 7.800000000028656 + }, + { + "index": 1, + "durationMs": 106.69999999925494, + "operationCount": 15, + "perOperationMs": 7.113333333283663 + }, + { + "index": 2, + "durationMs": 102.50000000186265, + "operationCount": 16, + "perOperationMs": 6.406250000116415 + } + ], + "medianMs": 7.113333333283663, + "p95Ms": 7.731333333354157, + "madMs": 0.6866666667449932, + "validation": [ + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + }, + { + "checkpoint": "geometry-unresized.1m builds exact uniform geometry", + "expected": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "passed": true + }, + { + "checkpoint": "grid remains mounted and accessibility-labelled", + "expected": "true", + "observed": "true", + "passed": true + }, + { + "checkpoint": "canonical row count", + "expected": "100000", + "observed": "100000", + "passed": true + }, + { + "checkpoint": "canonical sentinel checksum", + "expected": "fnv1a32:e2d3498b", + "observed": "fnv1a32:e2d3498b", + "passed": true + } + ], + "memory": { + "beforeBytes": 63603718, + "afterBytes": 75934218, + "deltaBytes": 12330500 + }, + "resources": { + "compiledFormats": 0, + "numberFormatters": 0, + "dateTimeFormatters": 0, + "formatCacheEntries": 0, + "numberFormatterCacheEntries": 0, + "dateTimeFormatterCacheEntries": 0 + }, + "mergeResources": { + "indexConstructions": 0, + "candidatesExamined": 0 + } + } + ], + "completeness": { + "expectedKeys": [ + "round=1;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=1;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=1;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=1;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll", + "round=2;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=2;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=2;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=2;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll", + "round=3;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=3;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=3;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=3;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll" + ], + "observedKeys": [ + "round=1;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=1;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=1;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=1;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll", + "round=2;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=2;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=2;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=2;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll", + "round=3;engine=sheetwrite;rows=100000;scenario=formula-dense.paint", + "round=3;engine=sheetwrite;rows=100000;scenario=geometry-unresized.1m", + "round=3;engine=sheetwrite;rows=100000;scenario=scroll-fractional.same-window", + "round=3;engine=sheetwrite;rows=100000;scenario=text-heavy.long-scroll" + ], + "missingKeys": [], + "duplicateKeys": [], + "unexpectedKeys": [], + "failedKeys": [], + "complete": true, + "successful": true + }, + "reproductionCommands": [ + "bun run --filter '@sheetwrite/bench' bench:render:diagnostic" + ] +} diff --git a/bench/results/render-diagnostics.md b/bench/results/render-diagnostics.md new file mode 100644 index 00000000..b9585c7c --- /dev/null +++ b/bench/results/render-diagnostics.md @@ -0,0 +1,47 @@ +# Auditable render benchmark + +Protocol version: **1** +Run ID: `498ce36d-ded1-426a-bf01-66d02257fcd3` +Matrix: **complete and successful** + +## Environment + +| Field | Value | +|:--|:--| +| Commit | `572c15cb27d428d987c59f57fa69f81ebddfedcc` (clean) | +| Timestamp | 2026-07-27T14:41:01.920Z | +| Runtime | Bun 1.3.14; Node 24.3.0 | +| Browser | 149.0.7827.55 | +| OS / arch | linux 7.1.3-2-cachyos / x64 | +| CPU | 12th Gen Intel(R) Core(TM) i9-12900H | +| Engines | Sheetwrite 0.3.1; Handsontable 18.0.0 | +| Dataset | seed 1592639710; 100,000 rows = `fnv1a32:178eac66` | +| Viewport | 640 × 480 | +| Sampling | 1 excluded warmup aggregate(s), 3 measured aggregate(s), minimum 100 ms each | +| Counterbalance | seed 1371602926; round 1: sheetwrite; round 2: sheetwrite; round 3: sheetwrite | +| Browser launches | 3 attempt(s); 0 failed attempt(s), all recorded in raw JSON | + +Every cell below is linked to the raw JSON. Timings are per logical operation and use every measured sample; p95 is linearly interpolated and MAD is the median absolute deviation. Setup, cleanup, and declared warmups are excluded. + +## Results + +| round | rows | scenario / raw identity | Sheetwrite | Handsontable | +|---:|---:|:--|:--|:--| +| 1 | 100,000 | [`r1-100000-formula-dense.paint`](./render-results.json) | median 0.45387 ms; p95 0.50812; MAD 0.03521; 3 samples / 773 ops | +| 1 | 100,000 | [`r1-100000-text-heavy.long-scroll`](./render-results.json) | median 4014.6 ms; p95 4053.3; MAD 43.000; 3 samples / 3 ops | +| 1 | 100,000 | [`r1-100000-scroll-fractional.same-window`](./render-results.json) | median 0.64774 ms; p95 0.71355; MAD 0.00351; 3 samples / 450 ops | +| 1 | 100,000 | [`r1-100000-geometry-unresized.1m`](./render-results.json) | median 6.900 ms; p95 7.228; MAD 0.36429; 3 samples / 46 ops | +| 2 | 100,000 | [`r2-100000-formula-dense.paint`](./render-results.json) | median 0.41056 ms; p95 0.41658; MAD 0.00669; 3 samples / 852 ops | +| 2 | 100,000 | [`r2-100000-text-heavy.long-scroll`](./render-results.json) | median 4126.8 ms; p95 4216.1; MAD 99.200; 3 samples / 3 ops | +| 2 | 100,000 | [`r2-100000-scroll-fractional.same-window`](./render-results.json) | median 0.64167 ms; p95 0.65209; MAD 0.01158; 3 samples / 476 ops | +| 2 | 100,000 | [`r2-100000-geometry-unresized.1m`](./render-results.json) | median 6.987 ms; p95 8.506; MAD 0.98667; 3 samples / 44 ops | +| 3 | 100,000 | [`r3-100000-formula-dense.paint`](./render-results.json) | median 0.46338 ms; p95 0.49322; MAD 0.01849; 3 samples / 711 ops | +| 3 | 100,000 | [`r3-100000-text-heavy.long-scroll`](./render-results.json) | median 3959.7 ms; p95 4255.4; MAD 102.2; 3 samples / 3 ops | +| 3 | 100,000 | [`r3-100000-scroll-fractional.same-window`](./render-results.json) | median 0.80480 ms; p95 0.97602; MAD 0.11308; 3 samples / 371 ops | +| 3 | 100,000 | [`r3-100000-geometry-unresized.1m`](./render-results.json) | median 7.113 ms; p95 7.731; MAD 0.68667; 3 samples / 44 ops | + +## Reproduce + +- `bun run --filter '@sheetwrite/bench' bench:render:diagnostic` + +The JSON artifact is authoritative. This Markdown file is generated from it and must not be edited by hand.