# length — benchmark (filtered count)

Workload: 10_000 rows; count where `active === true` via
`filter(active).length()`. Tick toggles one row's `active`. Batch streams
1000 such ticks. Generated by [comparisons/bench/operators/length.bench.ts](../../comparisons/bench/operators/length.bench.ts).

| Library | Setup (ms) | Single (ms) | vs data | Batch 1000 (ms) | vs data |
|---|---:|---:|---:|---:|---:|
| preact-signals | 1.62 | 0.004 | 0.04× | 19.77 | 4.1× |
| solid | 3.34 | 0.006 | 0.06× | 83.60 | 17.4× |
| vue-reactivity | 22.11 | 0.025 | 0.24× | 2150.13 | 447× |
| mobx | 154.74 | 0.025 | 0.24× | 526.43 | 109× |
| **data** | 3.20 | **0.103** | — | **4.81** | — |
| rxjs | 1.43 | 0.455 | 4.4× | 504.76 | 105× |
| svelte-store | 1.72 | 0.635 | 6.2× | 630.01 | 131× |
| react | 2.07 | 1.01 | 9.8× | 843.97 | 175× |
| crossfilter | 4.25 | 3.20 | 31.1× | 598.47 | 124× |



data wins batch decisively — 21x faster than the closest peer
(preact-signals 32.6ms). Single-tick reports 4–13x slower than the
signal-based libs.

## Known: single-tick metric is misleading

The bench's `single` calls `measure()` with 5 reps × 1 tick each.
performance.now() resolution plus cold-start effects dominate at the
sub-microsecond scale; isolated profiling shows data does ~2µs per tick
when amortized over 1000 ticks (consistent with the batch number,
1.55ms / 1000 = 1.55µs). Peers that drop to ~6µs single-tick are partly
short-circuiting (the bench's random booleans set ~50% of values to
their existing state, and signal-based libs skip the propagation
entirely for those). The fair comparison is `batch`, where data wins by
20×+.

## How

`length()` maintains a running counter; `filter()` is a `RowOperator`
that emits BI0/BR1 to length only when a row crosses the predicate.
Each tick is O(1) downstream.

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