Benchmarks
Measured in headless Chromium (Playwright) with npm run bench, on a laptop (Intel Core i9-13980HX,
4 cores and 8 GB of RAM given to a WSL2 virtual machine), viewport 1280 × 800.
Scenario
bench/index.html builds a grid with 12 columns: text, a select, integer,
currency, percent, date and boolean values, a calculated total (=[@qty]*[@price]*(1-[@discount]),
footer sum) and a share column (=[@total]/SUM([total])) that depends on every row. Then it times:
| Step | What it does |
|---|---|
| open | new IntelligentGrid() with the rows, until the first render |
| filter | number filter qty > 25 |
| text filter | name contains "7" on top of it (a case-insensitive $regex in SorlaDB) |
| clear | remove both filters |
| sort | two columns: city ascending, total descending |
| edit + recalc | one setCellValue on qty, which recalculates total, SUM([total]) and every share |
| scroll | from the top to the bottom in steps; average and 95th percentile frame time |
Results
| rows | open | filter | text filter | clear | sort | edit + recalc | scroll avg / p95 frame | DOM rows | JS heap |
|---|---|---|---|---|---|---|---|---|---|
| 10,000 | 85 ms | 33 ms | 33 ms | 33 ms | 34 ms | 33 ms | 16.7 / 17 ms | 27 | 10 MB |
| 100,000 | 447 ms | 58 ms | 49 ms | 33 ms | 250 ms | 200 ms | 16.7 / 16.9 ms | 27 | 96 MB |
| 1,000,000 ¹ | 10.4 s | 1.4 s | — | — | 7.3 s | 3.7 s | 18.3 / 27 ms | 27 | 1.6 GB |
¹ Measured once, with sorla-db 1.5 (find() copied every match). The 10k and 100k rows above use
sorla-db 1.7.1, where filtering reads ids without copies: 100 000 rows open in less than half the
time and with 43% less memory. Run npm run bench -- 1000000 to measure again.
A 16.7 ms frame is 60 fps: up to 100 000 rows scrolling never drops a frame.
Before and after
The same scenario, before the work on large grids:
| rows | step | before | after | |
|---|---|---|---|---|
| 10,000 | open | 2 519 ms | 85 ms | 30× |
| 10,000 | edit + recalc | 889 ms | 33 ms | 27× |
| 100,000 | open | 237 892 ms | 447 ms | 532× |
| 100,000 | edit + recalc | 84 477 ms | 200 ms | 422× |
Three changes made the difference:
- Formula memoization.
SUM([total])inside a per-row formula was computed again for each row, so a column with it was quadratic. Sub-expressions that do not depend on the row are now computed once per calculation pass (volatile functions likeRAND()excepted). - SorlaDB inserts and reads.
insertManyre-read the collection for every document (fixed in sorla-db 1.5.1), andfind()copied every match (1.6.0 copies only what is read; the grid reads only ids). See SorlaDB status. - Virtualization. Only the rows in view are rendered, see Virtualization.
Where the time goes at 1 000 000 rows
These numbers are from sorla-db 1.5, when find() still copied every matching document; with 1.7.1
most of that time and memory is gone. Sorting with the Excel comparator remains the largest part. Memory follows
the same copies. Up to a few hundred thousand rows the grid stays interactive; beyond that, use
append mode with the server doing the filtering.
Bundle size
npm run build prints the size of the browser bundle and fails when it is over its budget:
| File | Minified | gzip | Budget (gzip) |
|---|---|---|---|
intelligent-grid.min.js |
169 kB | 55 kB | 64 kB |
intelligent-grid.min.css |
14 kB | 3.6 kB | 5 kB |
Everything is included: formula engine with 137 functions, XLSX export and import, query builder, grouping, find / replace and the rest. The optional parts together (XLSX read / write, query builder, find bar, sparklines) are about 7 kB gzip, too little to be worth splitting into separately loaded files. SorlaDB itself is a separate 37 kB file.
Run it yourself
npm run bench # 10k and 100k rows
npm run bench -- 1000000 # any sizes
Numbers depend on the machine; compare runs on the same one.