What the engine costs
The engine on 100,000 and 1,000,000 rows — load, filter, sort, group, pivot and the memory it holds — measured on an Apple M4 Pro with the script to repeat it, and what the WebAssembly module costs a page.
The engine is Rust compiled to WebAssembly, so the question every page has to answer is what it buys and what it costs. This page gives the figures for the engine on its own: no browser, no rendering, no network — the module that answers a query, loaded in Node. They are measured, dated, and repeatable with one script.
What was measured
Measured on 14 November 2026 on an Apple M4 Pro (arm64), Node 24, with
scripts/measure-queries.mjs: one run thrown away, then nine, median shown with the
fastest and the slowest in brackets. The rows are ten columns of the conformance data set’s
shape — integers, text, an exact decimal, a float, a boolean, a date and a timestamp — as CSV.
| 100,000 rows (CSV 7.4 MB) | Median | Range |
|---|---|---|
| Load the CSV | 143 ms | — |
| Filter: country and amount, first 100 rows | 1.4 ms | 1.4–3.2 ms |
| Text search in a column, first 100 rows | 3.9 ms | 3.6–4.2 ms |
| Sort by amount, top 100 | 4.8 ms | 4.7–5.0 ms |
| Group by country, sum and count | 9.2 ms | 9.0–9.3 ms |
| Group by customer (500 groups), sum | 9.7 ms | 9.5–9.9 ms |
| Pivot: country and customer, sum and count | 30 ms | 30–32 ms |
| Module memory after the load | 23.6 MiB | |
| of which the columns | 9.8 MiB |
| 1,000,000 rows (CSV 74.6 MB) | Median | Range |
|---|---|---|
| Load the CSV | 1.3 s | — |
| Filter: country and amount, first 100 rows | 14 ms | 14–15 ms |
| Text search in a column, first 100 rows | 39 ms | 39–40 ms |
| Sort by amount, top 100 | 52 ms | 50–52 ms |
| Group by country, sum and count | 96 ms | 94–98 ms |
| Group by customer (500 groups), sum | 101 ms | 101–104 ms |
| Pivot: country and customer, sum and count | 305 ms | 301–307 ms |
| Module memory after the load | 213 MiB | |
| of which the columns | 98 MiB |
Ten times the rows cost about ten times the time and about nine times the memory: the engine scans typed columns, and nothing in these queries grows faster than the data.
Two numbers for the engine’s memory
The columns — the data itself, typed — take 9.8 MiB for 100,000 rows and 98 MiB for a million: about a hundred bytes a row. A 64-bit integer takes eight bytes and stays an integer, an exact decimal stays exact, a timestamp keeps its microseconds, and a missing value costs a bit in a bitmap rather than a boxed object.
The module memory is larger, 23.6 and 213 MiB, because it also holds the room that reading
the CSV needed, and WebAssembly memory only grows. That is the number a page pays, and the one
the engine’s budget guards: peakmark.toml fails a release whose scenario (100,000 rows loaded,
queried and pivoted) needs more than 26 MiB, or leaks from one run to the next.
The numbers are the same in the tab and on a server, because it is the same code.
What the whole page holds
The engine’s own memory is not what the browser shows. Measured with bench/ — the tool in the
repository — in Chromium with the engine in a Worker: the browser’s whole process tree, resident,
minus an empty page’s, with the rows loaded and after sorting, filtering and scrolling. It
includes the rows the test page keeps as JavaScript objects and the engine’s copy beside them.
| Rows | Handed over as | Loaded | After the queries | First rows on screen |
|---|---|---|---|---|
| 100,000 | CSV bytes | 169 MiB | 263 MiB | 139 ms |
| 100,000 | JavaScript objects | 193 MiB | 283 MiB | 191 ms |
| 1,000,000 | CSV bytes | 658 MiB | 784 MiB | 841 ms |
| 1,000,000 | JavaScript objects | 1,079 MiB | 1,102 MiB | 2.1 s |
So the engine is compact, not free: it keeps its own typed copy next to whatever the page holds, and handing it JavaScript objects makes it serialise them once, which costs time and memory that CSV does not. That copy is what the queries run on: through the Worker, in the browser, a sort of 100,000 rows takes 29 ms and of a million 48 ms; a filter 33 and 52 ms; clearing the filter and the sort 33 and 50 ms.
What a page pays
The engine is a module the page loads before its first query. Compressed with Brotli:
| Module | Size | Loaded |
|---|---|---|
The engine (engine/) |
about 160 KiB | when the page starts the engine |
All three elements (pkg/) |
about 348 KiB | with the elements |
The collation of a locale (collation/) |
about 330 KiB | only by a sort that names a locale |
The XLSX writer (xlsx/) |
about 240 KiB | only by an export to a workbook |
Sizes are kept inside budgets in peakmark.toml, so a release that grows one fails the build
until the growth is explained. A table of fifty rows does not need an engine, and the page
that only reads it should not pay for one.
In a worker
The engine runs in a Web Worker as it runs on the page, so a query does not block the main thread. The live demo queries 100,000 rows that way. The figures above are the engine’s own time; what the page adds is the transfer of the answer and the drawing of the rows on screen.
What these figures are not
- Not a browser measurement. Rendering, scrolling and the filter row are measured
separately by
scripts/measure-grid.mjs(just measure-grid), in Chromium and WebKit. - Not a comparison with other grids. One would need the same data, the same features and each product set up as its own documentation recommends, and its method published before its result. That is planned, not done; until then this page says nothing about anyone else.
- One machine. An M4 Pro is a fast laptop. A phone, or a laptop three years older, takes proportionally longer; run the script on the hardware your readers have.
Repeat it
just wasm-build
node scripts/measure-queries.mjs --rows 100000,1000000 --runs 9