Skip to content

Commit 21f9f42

Browse files
committed
Add benchmark suite and profile-guided optimizations
1 parent a070b4b commit 21f9f42

File tree

10 files changed

+966
-234
lines changed

10 files changed

+966
-234
lines changed

benchmarks/benchmark.html

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
<!--
2+
-- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
3+
-- ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░
4+
-- ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░
5+
-- ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░
6+
-- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
7+
-- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
8+
-- ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃
9+
-- ┃ * of the Regular Table library, distributed under the terms of the * ┃
10+
-- ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
11+
-- ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
-->
13+
14+
<!doctype html>
15+
<html>
16+
<head>
17+
<meta
18+
name="viewport"
19+
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
20+
/>
21+
<link rel="stylesheet" href="../dist/css/material.css" />
22+
<style>
23+
body {
24+
margin: 0;
25+
padding: 0;
26+
}
27+
28+
regular-table {
29+
width: 100vw;
30+
height: 100vh;
31+
box-sizing: border-box;
32+
}
33+
34+
td {
35+
color: #1078d1;
36+
}
37+
38+
/* Alternating row colors for visual complexity */
39+
tbody tr:nth-child(even) {
40+
background-color: #f8f9fa;
41+
}
42+
43+
/* Highlight negative values */
44+
td.negative {
45+
color: #dc3545;
46+
}
47+
48+
/* Highlight large values */
49+
td.large {
50+
font-weight: bold;
51+
color: #28a745;
52+
}
53+
</style>
54+
</head>
55+
56+
<body>
57+
<regular-table id="table"></regular-table>
58+
59+
<script type="module">
60+
import "../dist/esm/regular-table.js";
61+
62+
// Configuration - can be overridden via URL params
63+
const params = new URLSearchParams(window.location.search);
64+
const NUM_ROWS = parseInt(params.get("rows") || "100000", 10);
65+
const NUM_COLUMNS = parseInt(params.get("columns") || "10000", 10);
66+
const HEADER_DEPTH = parseInt(params.get("headerDepth") || "4", 10);
67+
const SIMULATE_LATENCY = parseInt(params.get("latency") || "0", 10);
68+
69+
// // Various formatters for complex data
70+
// const numberFormatter = new Intl.NumberFormat("en-us");
71+
// const currencyFormatter = new Intl.NumberFormat("en-us", {
72+
// style: "currency",
73+
// currency: "USD",
74+
// });
75+
76+
// const percentFormatter = new Intl.NumberFormat("en-us", {
77+
// style: "percent",
78+
// minimumFractionDigits: 2,
79+
// });
80+
81+
// const dateFormatter = new Intl.DateTimeFormat("en-us", {
82+
// year: "numeric",
83+
// month: "short",
84+
// day: "numeric",
85+
// });
86+
87+
// Pseudo-random number generator with seed for reproducibility
88+
function seededRandom(seed) {
89+
const x = Math.sin(seed) * 10000;
90+
return x - Math.floor(x);
91+
}
92+
93+
// Generate a random string of n characters
94+
function randomString(n) {
95+
const chars =
96+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
97+
let result = "";
98+
for (let i = 0; i < n; i++) {
99+
result += chars.charAt(
100+
Math.floor(Math.random() * chars.length),
101+
);
102+
}
103+
return result;
104+
}
105+
106+
const STRINGS = Array(200)
107+
.fill(0)
108+
.map(() => randomString(8));
109+
110+
// Generate complex cell data based on position
111+
function generateCellData(x, y) {
112+
const seed = x * 0xdeadbeef + y;
113+
const rand = seededRandom(seed);
114+
const type = x % 2;
115+
switch (type) {
116+
case 0:
117+
return Math.floor(rand * 1000000) - 500000;
118+
case 1:
119+
return STRINGS[Math.floor(rand * STRINGS.length)];
120+
}
121+
}
122+
123+
// Generate hierarchical headers
124+
function generateColumnHeaders(x) {
125+
const headers = [];
126+
if (HEADER_DEPTH >= 1) {
127+
headers.push(`Sector ${Math.floor(x / 100)}`);
128+
}
129+
130+
if (HEADER_DEPTH >= 2) {
131+
headers.push(`Group ${Math.floor(x / 10)}`);
132+
}
133+
134+
if (HEADER_DEPTH >= 3) {
135+
headers.push(`Col ${x}`);
136+
}
137+
138+
return headers;
139+
}
140+
141+
function generateRowHeaders(y) {
142+
const headers = [];
143+
if (HEADER_DEPTH >= 1) {
144+
headers.push(`Region ${Math.floor(y / 100)}`);
145+
}
146+
147+
if (HEADER_DEPTH >= 2) {
148+
headers.push(`Category ${Math.floor(y / 10)}`);
149+
}
150+
151+
if (HEADER_DEPTH >= 3) {
152+
headers.push(`Row ${y}`);
153+
}
154+
155+
return headers;
156+
}
157+
158+
// Complex data listener with optional simulated latency
159+
async function dataListener(x0, y0, x1, y1) {
160+
// Simulate network/computation latency if configured
161+
if (SIMULATE_LATENCY > 0) {
162+
await new Promise((resolve) =>
163+
setTimeout(resolve, SIMULATE_LATENCY),
164+
);
165+
}
166+
167+
const data = [];
168+
const column_headers = [];
169+
170+
for (let x = x0; x < x1; x++) {
171+
const column = [];
172+
data.push(column);
173+
column_headers.push(generateColumnHeaders(x));
174+
175+
for (let y = y0; y < y1; y++) {
176+
column.push(generateCellData(x, y));
177+
}
178+
}
179+
180+
const row_headers = [];
181+
for (let y = y0; y < y1; y++) {
182+
row_headers.push(generateRowHeaders(y));
183+
}
184+
185+
return {
186+
num_rows: NUM_ROWS,
187+
num_columns: NUM_COLUMNS,
188+
row_headers,
189+
column_headers,
190+
data,
191+
};
192+
}
193+
194+
// Style listener to add CSS classes based on cell content
195+
function styleListener() {
196+
const table = document.getElementById("table");
197+
for (const td of table.querySelectorAll("tbody td")) {
198+
const text = td.textContent;
199+
td.classList.remove("negative", "large");
200+
201+
// Check for negative values
202+
if (text.startsWith("-") || text.startsWith("($")) {
203+
td.classList.add("negative");
204+
}
205+
206+
// Check for large positive values
207+
const numMatch = text.replace(/[$,%]/g, "").match(/[\d.]+/);
208+
if (numMatch) {
209+
const num = parseFloat(numMatch[0]);
210+
if (num > 100000) {
211+
td.classList.add("large");
212+
}
213+
}
214+
}
215+
}
216+
217+
// Initialize table
218+
const table = document.getElementById("table");
219+
table.setDataListener(dataListener);
220+
table.addStyleListener(styleListener);
221+
await table.draw();
222+
223+
// Expose benchmark utilities to window for Playwright access
224+
window.benchmarkConfig = {
225+
numRows: NUM_ROWS,
226+
numColumns: NUM_COLUMNS,
227+
headerDepth: HEADER_DEPTH,
228+
simulateLatency: SIMULATE_LATENCY,
229+
};
230+
231+
window.scrollRight = async function (pixels = 100) {
232+
table.scrollLeft += pixels;
233+
await table.draw();
234+
};
235+
236+
window.scrollDown = async function (pixels = 100) {
237+
table.scrollTop += pixels;
238+
await table.draw();
239+
};
240+
241+
window.scrollDiagonal = async function (
242+
pixelsX = 50,
243+
pixelsY = 50,
244+
) {
245+
table.scrollLeft += pixelsX;
246+
table.scrollTop += pixelsY;
247+
await table.draw();
248+
};
249+
250+
window.scrollToPosition = async function (left, top) {
251+
table.scrollLeft = left;
252+
table.scrollTop = top;
253+
await table.draw();
254+
};
255+
256+
window.getTableMetrics = function () {
257+
const fps = table.getDrawFPS();
258+
return {
259+
fps: fps,
260+
scrollLeft: table.scrollLeft,
261+
scrollTop: table.scrollTop,
262+
scrollWidth: table.scrollWidth,
263+
scrollHeight: table.scrollHeight,
264+
visibleCells: table.querySelectorAll("tbody td").length,
265+
visibleRows: table.querySelectorAll("tbody tr").length,
266+
};
267+
};
268+
269+
window.resetScroll = async function () {
270+
table.scrollLeft = 0;
271+
table.scrollTop = 0;
272+
await table.draw();
273+
};
274+
275+
// Signal that the benchmark page is ready
276+
window.benchmarkReady = true;
277+
</script>
278+
</body>
279+
</html>

0 commit comments

Comments
 (0)