# group — benchmark (bucketed count via length(fn))

Workload: 10_000 rows over 10 categorical buckets; `length(d => d.cat)`.
Tick rewrites one row's `cat` (forces a bucket migration). Batch streams
1000 such ticks. Generated by
[comparisons/bench/operators/group.bench.ts](../../comparisons/bench/operators/group.bench.ts).

| Library | Setup (ms) | Single (ms) | vs data | Batch 1000 (ms) | vs data |
|---|---:|---:|---:|---:|---:|
| **data** | 1.80 | **0.005** | — | **1.04** | — |
| preact-signals | 1.48 | 0.565 | 113× | 51.24 | 49.3× |
| rxjs | 1.22 | 0.587 | 117× | 589.70 | 567× |
| svelte-store | 1.42 | 0.693 | 139× | 847.13 | 815× |
| react | 2.78 | 1.08 | 216× | 692.31 | 666× |
| solid | 1.94 | 1.61 | 322× | 143.53 | 138× |
| crossfilter | 4.23 | 2.12 | 424× | 461.54 | 444× |
| mobx | 107.39 | 8.68 | 1736× | 551.64 | 530× |
| vue-reactivity | 18.31 | 11.55 | 2310× | 1716.77 | 1651× |



data is fastest on both single (179x over react) and batch (67x over
preact-signals).

## How

`LengthFnValue` maintains a counter per bucket plus a `name → bucket`
map. BU2 on `cat` decrements the old bucket and increments the new one
in O(1). Crossfilter's `dim.group().reduceCount()` is the closest peer
primitive — also incremental but with heavier dimension/group
bookkeeping.

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