Benchmarks
How long the DataGrid engine takes to sort, filter and search a million rows. Measured on a four-core laptop with a script you can download and run against the same npm package.
Measured September 17, 2026 · @kanunilabs/datagrid-core 1.2.0 · Node 24.14.1 · Intel Core i7-11390H (4 cores, laptop), 16 GB RAM, Windows 11
- Sort by a number column
- 101 ms
- Filter by a value
- 18 ms
- Filter, then sort
- 40 ms
1,000,000 rows × 9 columns, columnar path, median of 3 runs.
1,000,000 rows × 9 columns
Each query ran twice over the same data. The row-based path is plain JavaScript over the row objects. The columnar path first turns each column into a typed array, and it is the exact function the grid's Web Worker runs. The grid switches to it above 20,000 rows, so at this size the gold dots are what a user waits for.
The text sort needs the most explaining. A comparison sort of a million strings calls the collator about 20 million times, which is why the row-based path takes 2.2 s. The columnar path ranks each distinct string with the collator once, keeps those ranks, and from then on sorts integers in 90 ms. But ranking is itself a sort, and in this dataset the product column has a million distinct values. So the first sort on that column took 1.8 s, not much better than the row-based path. A column with a handful of distinct values, like a region or a status, barely notices the first sort.
Search works the same way: the first search lower-cases every distinct value in the text and date columns (413 ms here), and later searches reuse that (65 ms). Both caches last until the data changes.
| Operation | Row-based | Columnar | Columnar, first query | Ratio |
|---|---|---|---|---|
| Sort, number column | 226 ms 208 ms – 309 ms | 101 ms 92 ms – 101 ms | 101 ms 68 ms – 130 ms | 2.2× |
| Sort, text column | 2.2 s 1.9 s – 2.3 s | 90 ms 80 ms – 106 ms | 1.8 s 1.6 s – 1.9 s | 24.2× |
| Sort, date column | 351 ms 344 ms – 355 ms | 92 ms 86 ms – 96 ms | 96 ms 69 ms – 102 ms | 3.8× |
| Sort, two columns | 433 ms 433 ms – 433 ms | 208 ms 202 ms – 218 ms | 245 ms 237 ms – 248 ms | 2.1× |
| Filter, equality | 159 ms 159 ms – 186 ms | 18 ms 17 ms – 19 ms | 22 ms 22 ms – 23 ms | 8.9× |
| Filter, 3 clauses (AND) | 206 ms 188 ms – 267 ms | 60 ms 55 ms – 63 ms | 82 ms 73 ms – 106 ms | 3.4× |
| Search, all columns | 737 ms 714 ms – 737 ms | 65 ms 50 ms – 69 ms | 413 ms 366 ms – 438 ms | 11.3× |
| Filter + sort | 205 ms 193 ms – 206 ms | 40 ms 38 ms – 44 ms | 41 ms 40 ms – 50 ms | 5.1× |
The chart shows medians. In the table, the big number is the median of 3 runs, each run itself a median of 5 timings, and the small numbers are the fastest and slowest run. “First query” is the first of the 5 timings in each run, before any cache exists.
100,000 rows
At this size both paths are quick, and the difference mostly matters for text. Encoding all 9 columns takes 200 ms. Under 20,000 rows the grid does not start the worker at all: moving the data to another thread would cost more than the sort.
| Operation | Row-based | Columnar | Columnar, first query | Ratio |
|---|---|---|---|---|
| Sort, number column | 10 ms 9.4 ms – 11 ms | 5.7 ms 5.5 ms – 5.8 ms | 7.9 ms 7.0 ms – 9.5 ms | 1.8× |
| Sort, text column | 179 ms 174 ms – 184 ms | 5.2 ms 5.0 ms – 5.5 ms | 147 ms 134 ms – 150 ms | 34.5× |
| Sort, date column | 20 ms 20 ms – 21 ms | 4.9 ms 4.5 ms – 5.0 ms | 5.0 ms 4.5 ms – 5.0 ms | 4.1× |
| Sort, two columns | 21 ms 20 ms – 22 ms | 11 ms 10 ms – 12 ms | 11 ms 10 ms – 12 ms | 1.9× |
| Filter, equality | 16 ms 16 ms – 18 ms | 1.8 ms 1.8 ms – 1.9 ms | 3.6 ms 3.5 ms – 3.8 ms | 9.1× |
| Filter, 3 clauses (AND) | 20 ms 20 ms – 20 ms | 8.2 ms 7.4 ms – 8.9 ms | 9.5 ms 7.4 ms – 11 ms | 2.5× |
| Search, all columns | 80 ms 70 ms – 82 ms | 5.9 ms 4.9 ms – 6.1 ms | 17 ms 17 ms – 18 ms | 13.5× |
| Filter + sort | 29 ms 24 ms – 31 ms | 9.5 ms 9.0 ms – 11 ms | 21 ms 20 ms – 23 ms | 3.0× |
What these numbers leave out
- Talking to the worker
- The columnar function is timed in the same process. In a browser the grid also posts the query to the worker and receives the new row order back as a transferred
Uint32Array, which is not copied. - Rendering
- Not in any number here. Only the rows in view are in the DOM, so a sorted million rows paints the same couple of dozen rows an unsorted one does.
- The first encode
- A column is encoded the first time a query needs it, and then kept until the data changes. Encoding all 9 columns took 3.3 s at a million rows and 200 ms at 100,000; a sort on one column encodes only that column, while a free-text search needs every visible one. It runs on the main thread because it calls your
valueGetterfunctions, which cannot be sent to a worker. From 100,000 rows up it yields to the browser as it goes, so the page keeps painting. - Other browsers
- Node and Chrome share the V8 engine. Firefox and Safari do not, and their numbers will differ.
- Noise
- This is a laptop, and runs vary. The row-based number sort took anywhere from 208 ms to 309 ms across the three runs, and a set of runs earlier the same day had the columnar number sort at 83 ms instead of 101 ms. Run the script a few times before trusting any single result, including ours.
Run it yourself
The script builds the same seeded dataset every time and imports the engine from npm, so there is nothing of ours on your machine except the package. Pass a row count as the first argument, and --json if you want the raw numbers.
npm install @kanunilabs/datagrid-core
curl -O https://kanunilabs.com/benchmarks/datagrid-pipeline.bench.mjs
node datagrid-pipeline.bench.mjs 1000000 datagrid-pipeline.bench.mjsWhy no other grid is on this page
We publish numbers only for code we can explain. Timing another library means picking its version, its configuration and the shape of its data on its behalf, and each of those can move the result more than the engine does. If you are comparing, run your own data through each candidate. The playground below loads a million rows in your browser, and the script above is short enough to adapt.