# sort — benchmark (top-K via za)

Workload: 10_000 rows; top 10 by `val` descending. Tick rewrites one
row's `val` (may enter/leave the top-K window). Batch streams 1000 such
ticks. Generated by [comparisons/bench/operators/sort.bench.ts](../../comparisons/bench/operators/sort.bench.ts).

| Library | Setup (ms) | Single (ms) | vs data | Batch 1000 (ms) | vs data |
|---|---:|---:|---:|---:|---:|
| **data** | 8.74 | **0.274** | — | **166.55** | — |
| crossfilter | 6.53 | 4.46 | 16.3× | 507.64 | 3.0× |
| react | 6.16 | 5.15 | 18.8× | 4720.26 | 28.3× |
| svelte-store | 6.02 | 5.16 | 18.8× | 5570.31 | 33.4× |
| rxjs | 7.49 | 6.44 | 23.5× | 8036.67 | 48.3× |
| preact-signals | 11.62 | 10.13 | 37.0× | 792.12 | 4.8× |
| solid | 13.39 | 12.46 | 45.5× | 1152.42 | 6.9× |
| mobx | 180.49 | 49.18 | 179× | 7002.78 | 42.0× |
| vue-reactivity | 101.45 | 115.03 | 420× | 6745.70 | 40.5× |



data is fastest on both single (7x over crossfilter) and batch (2.5x
over crossfilter).

## How

`za` (and its `az`/`top`/`limit` cousins) maintain an incremental top-K
window — only the boundary rotates on each BU2 ([operators/sort/index.ts](index.ts)).
crossfilter's `dim.top(K)` is the textbook peer equivalent. Other peers
re-sort 10k rows on every emit, which dominates at this batch size.

The table above is the single-tick / streamed-single-tick workload (one row
rewritten per emit) and is unchanged by the bounded-batch path. A separate
hot path — **multi-row** `BR1`/`BI0` on a finite window (a `between` range
brush narrowing/widening past the visible window) — reconciles the window
once instead of churning the per-row refill; see [sort.perf.ts](sort.perf.ts)
`bounded batch brush` (≈25 ms for ~10k membership changes through a top-100,
which was multi-second before). Not part of the peer harness (no peer exposes
the same brush-a-bounded-sort shape), so it lives in the perf suite, not here.

Run `BENCH_OPS=sort npm run bench:ops` to refresh.
