From 83d6745a98cea4eebf23a978072d89d0fffdeb06 Mon Sep 17 00:00:00 2001 From: chh-ay Date: Mon, 27 Jul 2026 22:33:31 +0700 Subject: [PATCH 1/2] perf(core): lazily allocate row geometry index --- .changeset/steady-trees-scroll.md | 5 + bench/src/render-scenarios.ts | 3 + bench/test/render-scenarios.test.ts | 2 + packages/core/src/fenwick.ts | 94 ++++++++++++++----- .../core/src/geometry-layout-controller.ts | 44 +++++---- packages/core/test/fenwick.test.ts | 31 ++++++ 6 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 .changeset/steady-trees-scroll.md diff --git a/.changeset/steady-trees-scroll.md b/.changeset/steady-trees-scroll.md new file mode 100644 index 00000000..7eda36c1 --- /dev/null +++ b/.changeset/steady-trees-scroll.md @@ -0,0 +1,5 @@ +--- +"@sheetwrite/core": patch +--- + +Keep uniform row geometry allocation-free until a custom row height requires dense indexing. diff --git a/bench/src/render-scenarios.ts b/bench/src/render-scenarios.ts index b74d0b37..6f67f617 100644 --- a/bench/src/render-scenarios.ts +++ b/bench/src/render-scenarios.ts @@ -39,6 +39,7 @@ export interface ScrollObservation { } export interface GeometryObservation { readonly count: number; + readonly backingStoreBytes: number; readonly totalHeight: number; readonly middleRow: number; readonly middleTop: number; @@ -51,6 +52,7 @@ export function measureUnresizedMillionRowGeometry(): GeometryObservation { const last = index.rowAtOffset(index.totalHeight - 1); return { count: index.count, + backingStoreBytes: index.backingStoreBytes, totalHeight: index.totalHeight, middleRow: middle.row, middleTop: middle.top, @@ -363,6 +365,7 @@ function scenarioActions( "geometry-unresized.1m builds exact uniform geometry", JSON.stringify({ count: 1_000_000, + backingStoreBytes: 0, totalHeight: 28_000_000, middleRow: 500_000, middleTop: 14_000_000, diff --git a/bench/test/render-scenarios.test.ts b/bench/test/render-scenarios.test.ts index a349da4d..a0e467cc 100644 --- a/bench/test/render-scenarios.test.ts +++ b/bench/test/render-scenarios.test.ts @@ -178,6 +178,7 @@ class FakeAdapter implements RenderBenchAdapter { measureUnresizedMillionRowGeometry() { return { count: 1_000_000, + backingStoreBytes: 0, totalHeight: 28_000_000, middleRow: 500_000, middleTop: 14_000_000, @@ -272,6 +273,7 @@ describe("scenario correctness checkpoints", () => { test("measures real million-row uniform OffsetIndex geometry", () => { expect(measureUnresizedMillionRowGeometry()).toEqual({ count: 1_000_000, + backingStoreBytes: 0, totalHeight: 28_000_000, middleRow: 500_000, middleTop: 14_000_000, diff --git a/packages/core/src/fenwick.ts b/packages/core/src/fenwick.ts index f413d066..07929cff 100644 --- a/packages/core/src/fenwick.ts +++ b/packages/core/src/fenwick.ts @@ -1,36 +1,47 @@ -// Fenwick (binary-indexed) tree over row heights: O(log n) scrollTop<->row and -// O(log n) single-row height updates. A parallel `heights` array keeps single-row -// reads and structural rebuilds O(1)/O(n) respectively; structural edits are rare. +// Uniform row geometry stays arithmetic and allocation-free. The first non-default +// height materializes parallel height/Fenwick arrays for O(log n) updates and lookups; +// structural rebuilds remain O(n) and are rare. export class OffsetIndex { private n: number; private readonly defaultHeight: number; - private heights: Float64Array; - /** 1-indexed Fenwick tree of heights. */ - private tree: Float64Array; + private heights: Float64Array | undefined; + /** 1-indexed Fenwick tree of heights; absent while every row has the default height. */ + private tree: Float64Array | undefined; private total: number; constructor(count: number, defaultHeight: number) { this.n = count; this.defaultHeight = defaultHeight; - this.heights = new Float64Array(count); - this.heights.fill(defaultHeight); - this.tree = new Float64Array(count + 1); this.total = count * defaultHeight; + } + + /** Allocate the dense index only when the first non-default height requires it. */ + private materialize(): void { + const heights = new Float64Array(this.n); + heights.fill(this.defaultHeight); + const tree = new Float64Array(this.n + 1); + this.heights = heights; + this.tree = tree; this.build(); } /** O(n) Fenwick construction from `heights`; also refreshes `total`. */ private build(): void { - const { tree, heights, n } = this; + const heights = this.heights; + const tree = this.tree; + if (!heights || !tree) { + this.total = this.n * this.defaultHeight; + return; + } tree.fill(0); let total = 0; - for (let i = 1; i <= n; i++) { + for (let i = 1; i <= this.n; i++) { const h = heights[i - 1] ?? 0; total += h; tree[i] = (tree[i] ?? 0) + h; const j = i + (i & -i); - if (j <= n) tree[j] = (tree[j] ?? 0) + (tree[i] ?? 0); + if (j <= this.n) tree[j] = (tree[j] ?? 0) + (tree[i] ?? 0); } this.total = total; } @@ -47,27 +58,38 @@ export class OffsetIndex { return this.defaultHeight; } + /** Bytes reserved by the optional dense typed-array backing stores. */ + get backingStoreBytes(): number { + return (this.heights?.byteLength ?? 0) + (this.tree?.byteLength ?? 0); + } + heightOf(row: number): number { - return this.heights[row] ?? this.defaultHeight; + return this.heights?.[row] ?? this.defaultHeight; } setHeight(row: number, h: number): void { if (row < 0 || row >= this.n) return; - const delta = h - this.heights[row]!; + const current = this.heights?.[row] ?? this.defaultHeight; + const delta = h - current; if (delta === 0) return; - this.heights[row] = h; + if (!this.heights || !this.tree) this.materialize(); + const heights = this.heights!; + const tree = this.tree!; + heights[row] = h; this.total += delta; for (let i = row + 1; i <= this.n; i += i & -i) { - this.tree[i] = (this.tree[i] ?? 0) + delta; + tree[i] = (tree[i] ?? 0) + delta; } } /** Sum of heights of rows [0, row); i.e. the top Y of `row`. */ offsetOf(row: number): number { let r = Math.max(0, Math.min(row, this.n)); + const tree = this.tree; + if (!tree) return r * this.defaultHeight; let sum = 0; while (r > 0) { - sum += this.tree[r] ?? 0; + sum += tree[r] ?? 0; r -= r & -r; } return sum; @@ -84,6 +106,11 @@ export class OffsetIndex { const last = this.n - 1; return { row: last, top: this.offsetOf(last) }; } + const tree = this.tree; + if (!tree) { + const row = Math.floor(offset / this.defaultHeight); + return { row, top: row * this.defaultHeight }; + } // Largest `pos` with prefix(pos) <= offset. let pos = 0; let remaining = offset; @@ -91,9 +118,9 @@ export class OffsetIndex { while (1 << (logn + 1) <= this.n) logn++; for (let k = logn; k >= 0; k--) { const next = pos + (1 << k); - if (next <= this.n && (this.tree[next] ?? 0) <= remaining) { + if (next <= this.n && (tree[next] ?? 0) <= remaining) { pos = next; - remaining -= this.tree[pos] ?? 0; + remaining -= tree[pos] ?? 0; } } // `pos` rows fit entirely above `offset`, so `offset` is inside row `pos`. @@ -103,10 +130,25 @@ export class OffsetIndex { insertRows(at: number, count: number, height = this.defaultHeight): void { if (count <= 0) return; const clamp = Math.max(0, Math.min(at, this.n)); + const heights = this.heights; + if (!heights) { + this.n += count; + if (height === this.defaultHeight) { + this.total = this.n * this.defaultHeight; + return; + } + const next = new Float64Array(this.n); + next.fill(this.defaultHeight); + next.fill(height, clamp, clamp + count); + this.heights = next; + this.tree = new Float64Array(this.n + 1); + this.build(); + return; + } const next = new Float64Array(this.n + count); - next.set(this.heights.subarray(0, clamp), 0); + next.set(heights.subarray(0, clamp), 0); next.fill(height, clamp, clamp + count); - next.set(this.heights.subarray(clamp), clamp + count); + next.set(heights.subarray(clamp), clamp + count); this.heights = next; this.n += count; this.tree = new Float64Array(this.n + 1); @@ -116,9 +158,15 @@ export class OffsetIndex { removeRows(at: number, count: number): void { if (count <= 0 || at >= this.n) return; const c = Math.min(count, this.n - at); + const heights = this.heights; + if (!heights) { + this.n -= c; + this.total = this.n * this.defaultHeight; + return; + } const next = new Float64Array(this.n - c); - next.set(this.heights.subarray(0, at), 0); - next.set(this.heights.subarray(at + c), at); + next.set(heights.subarray(0, at), 0); + next.set(heights.subarray(at + c), at); this.heights = next; this.n -= c; this.tree = new Float64Array(this.n + 1); diff --git a/packages/core/src/geometry-layout-controller.ts b/packages/core/src/geometry-layout-controller.ts index 78263e31..f57ba974 100644 --- a/packages/core/src/geometry-layout-controller.ts +++ b/packages/core/src/geometry-layout-controller.ts @@ -46,19 +46,6 @@ function assertGeometryDimensions(rowCount: number, columnCount: number): void { } } -function createRowIndex(rowCount: number, defaultHeight: number): OffsetIndex { - try { - return new OffsetIndex(rowCount, defaultHeight); - } catch (error) { - if (!(error instanceof RangeError)) throw error; - throw new SnapshotResourceError( - "maxRowsPerSheet", - DEFAULT_SNAPSHOT_RESOURCE_LIMITS.maxRowsPerSheet, - rowCount, - { cause: error }, - ); - } -} /** Owns row/column indexes, scaled scrolling, viewport windows, and frozen-pane mapping. */ export class GeometryLayoutController { private rowIndex: OffsetIndex; @@ -79,8 +66,7 @@ export class GeometryLayoutController { assertGeometryDimensions(sheet.rowCount, sheet.columns.length); this.visibleColumnIndices = visibleColumns(sheet); this.columnIndex = buildColumnIndex(sheet, this.visibleColumnIndices, options.zoom()); - this.rowIndex = createRowIndex(sheet.rowCount, options.theme().rowHeight); - this.applyRowHeights(sheet); + this.rowIndex = this.createRowIndex(sheet, sheet.rowCount); this.scrollScale = new ScaledScroll( this.rowIndex.totalHeight + options.theme().headerHeight, viewportHeight, @@ -351,8 +337,7 @@ export class GeometryLayoutController { rebuildRows(rowCount = this.options.sheet().rowCount): void { const sheet = this.options.sheet(); assertGeometryDimensions(rowCount, sheet.columns.length); - this.rowIndex = createRowIndex(rowCount, this.options.theme().rowHeight); - this.applyRowHeights(sheet); + this.rowIndex = this.createRowIndex(sheet, rowCount); } rebuildColumns(): void { @@ -375,14 +360,27 @@ export class GeometryLayoutController { }; } - private applyRowHeights(sheet: Sheet): void { - if (!sheet.rowHeights || sheet.rowHeights.size === 0) return; - for (const [dataRow, height] of sheet.rowHeights) { - const viewRow = this.toViewRow(dataRow); - if (viewRow !== null && viewRow < this.rowIndex.count) { - this.rowIndex.setHeight(viewRow, height * this.options.zoom()); + private createRowIndex(sheet: Sheet, rowCount: number): OffsetIndex { + const index = new OffsetIndex(rowCount, this.options.theme().rowHeight); + try { + if (sheet.rowHeights) { + for (const [dataRow, height] of sheet.rowHeights) { + const viewRow = this.toViewRow(dataRow); + if (viewRow !== null && viewRow < index.count) { + index.setHeight(viewRow, height * this.options.zoom()); + } + } } + } catch (error) { + if (!(error instanceof RangeError)) throw error; + throw new SnapshotResourceError( + "maxRowsPerSheet", + DEFAULT_SNAPSHOT_RESOURCE_LIMITS.maxRowsPerSheet, + rowCount, + { cause: error }, + ); } + return index; } } diff --git a/packages/core/test/fenwick.test.ts b/packages/core/test/fenwick.test.ts index 361dafd2..7194d28e 100644 --- a/packages/core/test/fenwick.test.ts +++ b/packages/core/test/fenwick.test.ts @@ -17,6 +17,17 @@ describe("OffsetIndex", () => { expect(idx.rowAtOffset(100000)).toEqual({ row: 9, top: 180 }); }); + it("keeps uniform geometry allocation-free through structural edits", () => { + const idx = new OffsetIndex(1_000_000, 28); + expect(idx.backingStoreBytes).toBe(0); + expect(idx.rowAtOffset(14_000_005)).toEqual({ row: 500_000, top: 14_000_000 }); + + idx.insertRows(500_000, 2, 28); + idx.removeRows(100, 1); + expect(idx.backingStoreBytes).toBe(0); + expect(idx.totalHeight).toBe(1_000_001 * 28); + }); + it("reflects a single-row height override in subsequent offsets", () => { const idx = new OffsetIndex(10, 20); idx.setHeight(2, 50); @@ -28,6 +39,26 @@ describe("OffsetIndex", () => { expect(idx.rowAtOffset(90)).toEqual({ row: 3, top: 90 }); }); + it("materializes the dense index on the first height override", () => { + const idx = new OffsetIndex(10, 20); + expect(idx.backingStoreBytes).toBe(0); + idx.setHeight(2, 50); + expect(idx.backingStoreBytes).toBe(10 * 8 + 11 * 8); + expect(idx.rowAtOffset(85)).toEqual({ row: 2, top: 40 }); + }); + + it("materializes and indexes a non-default inserted band", () => { + const idx = new OffsetIndex(3, 20); + idx.insertRows(1, 2, 35); + expect(idx.backingStoreBytes).toBe(5 * 8 + 6 * 8); + expect(idx.totalHeight).toBe(130); + expect([0, 1, 2, 3, 4].map((row) => idx.heightOf(row))).toEqual([20, 35, 35, 20, 20]); + expect(idx.offsetOf(3)).toBe(90); + expect(idx.rowAtOffset(54)).toEqual({ row: 1, top: 20 }); + expect(idx.rowAtOffset(55)).toEqual({ row: 2, top: 55 }); + expect(idx.rowAtOffset(90)).toEqual({ row: 3, top: 90 }); + }); + it("inserts and removes rows, updating the total height", () => { const idx = new OffsetIndex(10, 20); idx.insertRows(0, 2, 20); From 3cba7d453176e30ab499bfcdc2f2b8b36f9fed4f Mon Sep 17 00:00:00 2001 From: chh-ay Date: Mon, 27 Jul 2026 22:35:28 +0700 Subject: [PATCH 2/2] perf(bench): record lazy geometry results --- bench/results/render-diagnostics.json | 392 +++++++++++++------------- bench/results/render-diagnostics.md | 30 +- 2 files changed, 211 insertions(+), 211 deletions(-) diff --git a/bench/results/render-diagnostics.json b/bench/results/render-diagnostics.json index 4ee9a825..0abf9adb 100644 --- a/bench/results/render-diagnostics.json +++ b/bench/results/render-diagnostics.json @@ -1,10 +1,10 @@ { "protocolVersion": 1, - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "metadata": { - "commit": "572c15cb27d428d987c59f57fa69f81ebddfedcc", + "commit": "83d6745a98cea4eebf23a978072d89d0fffdeb06", "dirty": false, - "timestamp": "2026-07-27T14:41:01.920Z", + "timestamp": "2026-07-27T15:34:48.766Z", "bunVersion": "1.3.14", "nodeVersion": "24.3.0", "browserVersion": "149.0.7827.55", @@ -85,37 +85,37 @@ }, "results": [ { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 1, "engine": "sheetwrite", "rows": 100000, "scenarioId": "formula-dense.paint", "group": "formulae", "status": "success", - "operationCount": 773, + "operationCount": 763, "rawSamples": [ { "index": 0, - "durationMs": 105.3999999947846, - "operationCount": 205, - "perOperationMs": 0.5141463414379737 + "durationMs": 100.00000000186265, + "operationCount": 200, + "perOperationMs": 0.5000000000093132 }, { "index": 1, - "durationMs": 128.90000000223517, - "operationCount": 284, - "perOperationMs": 0.45387323944449004 + "durationMs": 104.90000000223517, + "operationCount": 279, + "perOperationMs": 0.3759856630904487 }, { "index": 2, - "durationMs": 118.90000000223517, + "durationMs": 107.49999999813735, "operationCount": 284, - "perOperationMs": 0.41866197183885623 + "perOperationMs": 0.3785211267540048 } ], - "medianMs": 0.45387323944449004, - "p95Ms": 0.5081190312386253, - "madMs": 0.03521126760563381, + "medianMs": 0.3785211267540048, + "p95Ms": 0.48785211268378237, + "madMs": 0.0025354636635561145, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -167,9 +167,9 @@ } ], "memory": { - "beforeBytes": 20159169, - "afterBytes": 25154572, - "deltaBytes": 4995403 + "beforeBytes": 20080377, + "afterBytes": 25141664, + "deltaBytes": 5061287 }, "resources": { "compiledFormats": 0, @@ -185,7 +185,7 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 1, "engine": "sheetwrite", "rows": 100000, @@ -196,26 +196,26 @@ "rawSamples": [ { "index": 0, - "durationMs": 4014.5999999996275, + "durationMs": 3969.7000000011176, "operationCount": 1, - "perOperationMs": 4014.5999999996275 + "perOperationMs": 3969.7000000011176 }, { "index": 1, - "durationMs": 3920.800000000745, + "durationMs": 4203.9000000003725, "operationCount": 1, - "perOperationMs": 3920.800000000745 + "perOperationMs": 4203.9000000003725 }, { "index": 2, - "durationMs": 4057.5999999996275, + "durationMs": 4022.5, "operationCount": 1, - "perOperationMs": 4057.5999999996275 + "perOperationMs": 4022.5 } ], - "medianMs": 4014.5999999996275, - "p95Ms": 4053.2999999996273, - "madMs": 43, + "medianMs": 4022.5, + "p95Ms": 4185.760000000335, + "madMs": 52.79999999888241, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -267,9 +267,9 @@ } ], "memory": { - "beforeBytes": 25184892, - "afterBytes": 54231561, - "deltaBytes": 29046669 + "beforeBytes": 20250857, + "afterBytes": 48982401, + "deltaBytes": 28731544 }, "resources": { "compiledFormats": 0, @@ -285,37 +285,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 1, "engine": "sheetwrite", "rows": 100000, "scenarioId": "scroll-fractional.same-window", "group": "view-scrolling", "status": "success", - "operationCount": 450, + "operationCount": 424, "rawSamples": [ { "index": 0, - "durationMs": 100.49999998696148, - "operationCount": 156, - "perOperationMs": 0.644230769147189 + "durationMs": 100.59999998472631, + "operationCount": 145, + "perOperationMs": 0.6937931033429401 }, { "index": 1, - "durationMs": 100.40000002086163, - "operationCount": 155, - "perOperationMs": 0.647741935618462 + "durationMs": 100.20000000484288, + "operationCount": 139, + "perOperationMs": 0.7208633093873589 }, { "index": 2, - "durationMs": 100.1999999973923, - "operationCount": 139, - "perOperationMs": 0.7208633093337575 + "durationMs": 100.59999999403954, + "operationCount": 140, + "perOperationMs": 0.7185714285288538 } ], - "medianMs": 0.647741935618462, - "p95Ms": 0.713551171962228, - "madMs": 0.0035111664712730306, + "medianMs": 0.7185714285288538, + "p95Ms": 0.7206341213015084, + "madMs": 0.0022918808585050687, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -367,9 +367,9 @@ } ], "memory": { - "beforeBytes": 54265593, - "afterBytes": 62823406, - "deltaBytes": 8557813 + "beforeBytes": 49016445, + "afterBytes": 34924577, + "deltaBytes": -14091868 }, "resources": { "compiledFormats": 0, @@ -385,37 +385,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 1, "engine": "sheetwrite", "rows": 100000, "scenarioId": "geometry-unresized.1m", "group": "geometry", "status": "success", - "operationCount": 46, + "operationCount": 2415151, "rawSamples": [ { "index": 0, - "durationMs": 103.5, - "operationCount": 15, - "perOperationMs": 6.9 + "durationMs": 100, + "operationCount": 843835, + "perOperationMs": 0.00011850658007785883 }, { "index": 1, - "durationMs": 101.70000000111759, - "operationCount": 14, - "perOperationMs": 7.264285714365542 + "durationMs": 100.00000000186265, + "operationCount": 801253, + "perOperationMs": 0.0001248045249151799 }, { "index": 2, - "durationMs": 103.5, - "operationCount": 17, - "perOperationMs": 6.088235294117647 + "durationMs": 100.00000002607703, + "operationCount": 770063, + "perOperationMs": 0.00012985950503540235 } ], - "medianMs": 6.9, - "p95Ms": 7.227857142928988, - "madMs": 0.3642857143655416, + "medianMs": 0.0001248045249151799, + "p95Ms": 0.0001293540070233801, + "madMs": 0.000005054980120222446, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -437,8 +437,8 @@ }, { "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}", + "expected": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", "passed": true }, { @@ -461,9 +461,9 @@ } ], "memory": { - "beforeBytes": 62832054, - "afterBytes": 107770594, - "deltaBytes": 44938540 + "beforeBytes": 34934553, + "afterBytes": 45551173, + "deltaBytes": 10616620 }, "resources": { "compiledFormats": 0, @@ -479,7 +479,7 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 2, "engine": "sheetwrite", "rows": 100000, @@ -490,26 +490,26 @@ "rawSamples": [ { "index": 0, - "durationMs": 118.50000000186265, + "durationMs": 108.70000000111759, "operationCount": 284, - "perOperationMs": 0.41725352113331915 + "perOperationMs": 0.3827464788771746 }, { "index": 1, - "durationMs": 116.59999999776483, + "durationMs": 119.40000000037253, "operationCount": 284, - "perOperationMs": 0.41056338027381983 + "perOperationMs": 0.42042253521257933 }, { "index": 2, - "durationMs": 108.50000000372529, + "durationMs": 112.70000000298023, "operationCount": 284, - "perOperationMs": 0.382042253534244 + "perOperationMs": 0.39683098592598676 } ], - "medianMs": 0.41056338027381983, - "p95Ms": 0.4165845070473692, - "madMs": 0.006690140859499316, + "medianMs": 0.39683098592598676, + "p95Ms": 0.4180633802839201, + "madMs": 0.014084507048812145, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -561,9 +561,9 @@ } ], "memory": { - "beforeBytes": 19939713, - "afterBytes": 29592592, - "deltaBytes": 9652879 + "beforeBytes": 19967689, + "afterBytes": 29439016, + "deltaBytes": 9471327 }, "resources": { "compiledFormats": 0, @@ -579,7 +579,7 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 2, "engine": "sheetwrite", "rows": 100000, @@ -590,26 +590,26 @@ "rawSamples": [ { "index": 0, - "durationMs": 4226, + "durationMs": 3991.0999999996275, "operationCount": 1, - "perOperationMs": 4226 + "perOperationMs": 3991.0999999996275 }, { "index": 1, - "durationMs": 3964.4000000003725, + "durationMs": 3918.7999999988824, "operationCount": 1, - "perOperationMs": 3964.4000000003725 + "perOperationMs": 3918.7999999988824 }, { "index": 2, - "durationMs": 4126.799999998882, + "durationMs": 3731.199999999255, "operationCount": 1, - "perOperationMs": 4126.799999998882 + "perOperationMs": 3731.199999999255 } ], - "medianMs": 4126.799999998882, - "p95Ms": 4216.079999999888, - "madMs": 99.20000000111759, + "medianMs": 3918.7999999988824, + "p95Ms": 3983.869999999553, + "madMs": 72.30000000074506, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -661,9 +661,9 @@ } ], "memory": { - "beforeBytes": 20251253, - "afterBytes": 57249885, - "deltaBytes": 36998632 + "beforeBytes": 20260401, + "afterBytes": 52970561, + "deltaBytes": 32710160 }, "resources": { "compiledFormats": 0, @@ -679,37 +679,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 2, "engine": "sheetwrite", "rows": 100000, "scenarioId": "scroll-fractional.same-window", "group": "view-scrolling", "status": "success", - "operationCount": 476, + "operationCount": 453, "rawSamples": [ { "index": 0, - "durationMs": 100.50000000558794, - "operationCount": 166, - "perOperationMs": 0.6054216867806502 + "durationMs": 100.49999999627471, + "operationCount": 145, + "perOperationMs": 0.6931034482501705 }, { "index": 1, - "durationMs": 100.10000000707805, - "operationCount": 156, - "perOperationMs": 0.6416666667120388 + "durationMs": 100.79999999701977, + "operationCount": 150, + "perOperationMs": 0.6719999999801318 }, { "index": 2, - "durationMs": 100.59999999217689, - "operationCount": 154, - "perOperationMs": 0.6532467531959538 + "durationMs": 100.50000000558794, + "operationCount": 158, + "perOperationMs": 0.6360759494024553 } ], - "medianMs": 0.6416666667120388, - "p95Ms": 0.6520887445475623, - "madMs": 0.011580086483915064, + "medianMs": 0.6719999999801318, + "p95Ms": 0.6909931034231666, + "madMs": 0.021103448270038627, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -761,9 +761,9 @@ } ], "memory": { - "beforeBytes": 57283917, - "afterBytes": 66147714, - "deltaBytes": 8863797 + "beforeBytes": 53004617, + "afterBytes": 61620022, + "deltaBytes": 8615405 }, "resources": { "compiledFormats": 0, @@ -779,37 +779,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 2, "engine": "sheetwrite", "rows": 100000, "scenarioId": "geometry-unresized.1m", "group": "geometry", "status": "success", - "operationCount": 44, + "operationCount": 2463523, "rawSamples": [ { "index": 0, - "durationMs": 104.09999999962747, - "operationCount": 12, - "perOperationMs": 8.674999999968955 + "durationMs": 100.09999998286366, + "operationCount": 738813, + "perOperationMs": 0.00013548759968065488 }, { "index": 1, - "durationMs": 102, - "operationCount": 17, - "perOperationMs": 6 + "durationMs": 100.00000000372529, + "operationCount": 896246, + "perOperationMs": 0.00011157650913223076 }, { "index": 2, - "durationMs": 104.80000000074506, - "operationCount": 15, - "perOperationMs": 6.986666666716337 + "durationMs": 100.00000000186265, + "operationCount": 828464, + "perOperationMs": 0.0001207053052418242 } ], - "medianMs": 6.986666666716337, - "p95Ms": 8.506166666643693, - "madMs": 0.9866666667163368, + "medianMs": 0.0001207053052418242, + "p95Ms": 0.0001340093702367718, + "madMs": 0.000009128796109593435, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -831,8 +831,8 @@ }, { "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}", + "expected": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", "passed": true }, { @@ -855,9 +855,9 @@ } ], "memory": { - "beforeBytes": 66156374, - "afterBytes": 291152202, - "deltaBytes": 224995828 + "beforeBytes": 61628694, + "afterBytes": 48585350, + "deltaBytes": -13043344 }, "resources": { "compiledFormats": 0, @@ -873,37 +873,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 3, "engine": "sheetwrite", "rows": 100000, "scenarioId": "formula-dense.paint", "group": "formulae", "status": "success", - "operationCount": 711, + "operationCount": 731, "rawSamples": [ { "index": 0, - "durationMs": 100.09999999776483, - "operationCount": 225, - "perOperationMs": 0.4448888888789548 + "durationMs": 106.29999999888241, + "operationCount": 284, + "perOperationMs": 0.37429577464395214 }, { "index": 1, - "durationMs": 100.29999999701977, - "operationCount": 202, - "perOperationMs": 0.4965346534505929 + "durationMs": 100, + "operationCount": 271, + "perOperationMs": 0.36900369003690037 }, { "index": 2, - "durationMs": 131.59999999776483, - "operationCount": 284, - "perOperationMs": 0.4633802816822705 + "durationMs": 100.00000000186265, + "operationCount": 176, + "perOperationMs": 0.5681818181924014 } ], - "medianMs": 0.4633802816822705, - "p95Ms": 0.49321921627376064, - "madMs": 0.018491392803315743, + "medianMs": 0.37429577464395214, + "p95Ms": 0.5487932138375565, + "madMs": 0.005292084607051772, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -955,9 +955,9 @@ } ], "memory": { - "beforeBytes": 19738833, - "afterBytes": 24694324, - "deltaBytes": 4955491 + "beforeBytes": 20228497, + "afterBytes": 25818324, + "deltaBytes": 5589827 }, "resources": { "compiledFormats": 0, @@ -973,7 +973,7 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 3, "engine": "sheetwrite", "rows": 100000, @@ -984,26 +984,26 @@ "rawSamples": [ { "index": 0, - "durationMs": 3857.5, + "durationMs": 3976.60000000149, "operationCount": 1, - "perOperationMs": 3857.5 + "perOperationMs": 3976.60000000149 }, { "index": 1, - "durationMs": 3959.699999999255, + "durationMs": 4009.5, "operationCount": 1, - "perOperationMs": 3959.699999999255 + "perOperationMs": 4009.5 }, { "index": 2, - "durationMs": 4288.300000000745, + "durationMs": 4007.199999999255, "operationCount": 1, - "perOperationMs": 4288.300000000745 + "perOperationMs": 4007.199999999255 } ], - "medianMs": 3959.699999999255, - "p95Ms": 4255.440000000596, - "madMs": 102.19999999925494, + "medianMs": 4007.199999999255, + "p95Ms": 4009.2699999999254, + "madMs": 2.300000000745058, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -1055,9 +1055,9 @@ } ], "memory": { - "beforeBytes": 20257673, - "afterBytes": 56376837, - "deltaBytes": 36119164 + "beforeBytes": 20258453, + "afterBytes": 57446141, + "deltaBytes": 37187688 }, "resources": { "compiledFormats": 0, @@ -1073,37 +1073,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 3, "engine": "sheetwrite", "rows": 100000, "scenarioId": "scroll-fractional.same-window", "group": "view-scrolling", "status": "success", - "operationCount": 371, + "operationCount": 485, "rawSamples": [ { "index": 0, - "durationMs": 100.300000006333, - "operationCount": 145, - "perOperationMs": 0.6917241379747103 + "durationMs": 100.09999999962747, + "operationCount": 161, + "perOperationMs": 0.6217391304324688 }, { "index": 1, - "durationMs": 100.59999999590218, - "operationCount": 125, - "perOperationMs": 0.8047999999672174 + "durationMs": 100.40000000223517, + "operationCount": 167, + "perOperationMs": 0.6011976048038035 }, { "index": 2, - "durationMs": 100.49999999627471, - "operationCount": 101, - "perOperationMs": 0.995049504913611 + "durationMs": 100.40000000596046, + "operationCount": 157, + "perOperationMs": 0.6394904458978373 } ], - "medianMs": 0.8047999999672174, - "p95Ms": 0.9760245544189716, - "madMs": 0.11307586199250708, + "medianMs": 0.6217391304324688, + "p95Ms": 0.6377153143513005, + "madMs": 0.017751315465368567, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -1155,9 +1155,9 @@ } ], "memory": { - "beforeBytes": 56410885, - "afterBytes": 63595066, - "deltaBytes": 7184181 + "beforeBytes": 57480193, + "afterBytes": 66643534, + "deltaBytes": 9163341 }, "resources": { "compiledFormats": 0, @@ -1173,37 +1173,37 @@ } }, { - "runId": "498ce36d-ded1-426a-bf01-66d02257fcd3", + "runId": "9b53c653-199a-4c15-b545-72c34cad1de7", "round": 3, "engine": "sheetwrite", "rows": 100000, "scenarioId": "geometry-unresized.1m", "group": "geometry", "status": "success", - "operationCount": 44, + "operationCount": 2512673, "rawSamples": [ { "index": 0, - "durationMs": 101.40000000037253, - "operationCount": 13, - "perOperationMs": 7.800000000028656 + "durationMs": 100, + "operationCount": 861993, + "perOperationMs": 0.00011601022282083497 }, { "index": 1, - "durationMs": 106.69999999925494, - "operationCount": 15, - "perOperationMs": 7.113333333283663 + "durationMs": 100.09999998286366, + "operationCount": 806832, + "perOperationMs": 0.00012406548077278004 }, { "index": 2, - "durationMs": 102.50000000186265, - "operationCount": 16, - "perOperationMs": 6.406250000116415 + "durationMs": 100.09999997913837, + "operationCount": 843848, + "perOperationMs": 0.00011862325914043569 } ], - "medianMs": 7.113333333283663, - "p95Ms": 7.731333333354157, - "madMs": 0.6866666667449932, + "medianMs": 0.00011862325914043569, + "p95Ms": 0.00012352125860954561, + "madMs": 0.0000026130363196007254, "validation": [ { "checkpoint": "grid remains mounted and accessibility-labelled", @@ -1225,8 +1225,8 @@ }, { "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}", + "expected": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", + "observed": "{\"count\":1000000,\"backingStoreBytes\":0,\"totalHeight\":28000000,\"middleRow\":500000,\"middleTop\":14000000,\"lastRow\":999999,\"lastTop\":27999972}", "passed": true }, { @@ -1249,9 +1249,9 @@ } ], "memory": { - "beforeBytes": 63603718, - "afterBytes": 75934218, - "deltaBytes": 12330500 + "beforeBytes": 66652194, + "afterBytes": 70127578, + "deltaBytes": 3475384 }, "resources": { "compiledFormats": 0, diff --git a/bench/results/render-diagnostics.md b/bench/results/render-diagnostics.md index b9585c7c..164c9f73 100644 --- a/bench/results/render-diagnostics.md +++ b/bench/results/render-diagnostics.md @@ -1,15 +1,15 @@ # Auditable render benchmark Protocol version: **1** -Run ID: `498ce36d-ded1-426a-bf01-66d02257fcd3` +Run ID: `9b53c653-199a-4c15-b545-72c34cad1de7` Matrix: **complete and successful** ## Environment | Field | Value | |:--|:--| -| Commit | `572c15cb27d428d987c59f57fa69f81ebddfedcc` (clean) | -| Timestamp | 2026-07-27T14:41:01.920Z | +| Commit | `83d6745a98cea4eebf23a978072d89d0fffdeb06` (clean) | +| Timestamp | 2026-07-27T15:34:48.766Z | | Runtime | Bun 1.3.14; Node 24.3.0 | | Browser | 149.0.7827.55 | | OS / arch | linux 7.1.3-2-cachyos / x64 | @@ -27,18 +27,18 @@ Every cell below is linked to the raw JSON. Timings are per logical operation an | 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 | +| 1 | 100,000 | [`r1-100000-formula-dense.paint`](./render-results.json) | median 0.37852 ms; p95 0.48785; MAD 0.00254; 3 samples / 763 ops | +| 1 | 100,000 | [`r1-100000-text-heavy.long-scroll`](./render-results.json) | median 4022.5 ms; p95 4185.8; MAD 52.800; 3 samples / 3 ops | +| 1 | 100,000 | [`r1-100000-scroll-fractional.same-window`](./render-results.json) | median 0.71857 ms; p95 0.72063; MAD 0.00229; 3 samples / 424 ops | +| 1 | 100,000 | [`r1-100000-geometry-unresized.1m`](./render-results.json) | median 0.00012 ms; p95 0.00013; MAD 0.00001; 3 samples / 2415151 ops | +| 2 | 100,000 | [`r2-100000-formula-dense.paint`](./render-results.json) | median 0.39683 ms; p95 0.41806; MAD 0.01408; 3 samples / 852 ops | +| 2 | 100,000 | [`r2-100000-text-heavy.long-scroll`](./render-results.json) | median 3918.8 ms; p95 3983.9; MAD 72.300; 3 samples / 3 ops | +| 2 | 100,000 | [`r2-100000-scroll-fractional.same-window`](./render-results.json) | median 0.67200 ms; p95 0.69099; MAD 0.02110; 3 samples / 453 ops | +| 2 | 100,000 | [`r2-100000-geometry-unresized.1m`](./render-results.json) | median 0.00012 ms; p95 0.00013; MAD 0.00001; 3 samples / 2463523 ops | +| 3 | 100,000 | [`r3-100000-formula-dense.paint`](./render-results.json) | median 0.37430 ms; p95 0.54879; MAD 0.00529; 3 samples / 731 ops | +| 3 | 100,000 | [`r3-100000-text-heavy.long-scroll`](./render-results.json) | median 4007.2 ms; p95 4009.3; MAD 2.300; 3 samples / 3 ops | +| 3 | 100,000 | [`r3-100000-scroll-fractional.same-window`](./render-results.json) | median 0.62174 ms; p95 0.63772; MAD 0.01775; 3 samples / 485 ops | +| 3 | 100,000 | [`r3-100000-geometry-unresized.1m`](./render-results.json) | median 0.00012 ms; p95 0.00012; MAD 0.00000; 3 samples / 2512673 ops | ## Reproduce