Benchmarks
Keyed benchmark results
Our local reproduction of js-framework-benchmark's keyed CPU, memory and size tests, run in headless Chrome against hand-written vanilla JS and React 18.3
with hooks. Times are the geometric mean of the slowdown against vanilla JS over nine keyed operations; the ratios move by about 0.1 between runs.
Keyed benchmark, geometric mean
Slowdown against hand-written vanilla JS, nine keyed operations
› table
| framework | Slowdown against hand-written vanilla JS, nine keyed operations |
|---|---|
| vanilla JS | ×1.00 |
| Melact | ×1.17 |
| React 18 | ×1.70 |
JavaScript of the benchmark app
gzip size of all JS in the production bundle
› table
| framework | gzip size of all JS in the production bundle |
|---|---|
| vanilla JS | 1.4 kB |
| Melact | 9.9 kB |
| React 18 | 45.6 kB |
Heap with 1,000 rows
After two forced garbage collections
› table
| framework | After two forced garbage collections |
|---|---|
| vanilla JS | 0.77 MB |
| Melact | 1.64 MB |
| React 18 | 3.17 MB |
Swap rows
Slowdown against vanilla JS, swapping two of 1,000 rows
› table
| framework | Slowdown against vanilla JS, swapping two of 1,000 rows |
|---|---|
| vanilla JS | ×1.00 |
| Melact | ×1.17 |
| React 18 | ×5.86 |
Swapping two rows of 1,000 moves two rows; React moves 998. A list re-renders only the rows whose item changed.
The figure
Where the time goes.
Pale: the work itself. Solid: the overhead on top of it.
Apps
Same markup, same stylesheet.
vanilla/
hand-written keyed DOM code in the style of the benchmark's vanillajs-keyed: one cloned <tr> template, event delegation on tbody, manual keyed operations
melact/
reason-react style: useReducer in an App component, a React.memo Row component
react/
React 18.3 with hooks, like the benchmark's react-hooks: useReducer, memo rows, createElement
Method
Measured like the real benchmark.
- Every sample is a fresh tab: load, the benchmark's warmups for that operation, then one measured click, sent as real mouse input.
- The measured click is traced. The time is the renderer main thread's busy time from the start of the click dispatch to the end of the first Paint and its Commit after it.
- CPU slowdown as in the benchmark: 4× for partial update, select, swap and clear, 2× for remove.
- After every measured click the page is checked: row count, every id in order, every label, the markup of every row, and which rows have class="danger". A fast but wrong result is reported, not counted silently.
- Memory: HeapProfiler.collectGarbage twice, then Runtime.getHeapUsage, after load, after creating 1,000 rows, and after five more create/clear cycles.
- Size: the gzip (-9) size of all JS in the production bundle.
Whole apps
Small, because the runtime is made of parts.
Every kind of child carries its own mount and patch code, and every hole its own writer, so a bundle contains only the parts its compiled code names. Whole
apps, runtime and the Melange stdlib they use included: the counter is 5.6 kB gzip with Melact; React 18.3 and react-dom alone are 45 kB.