You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .cursor/rules/benchmarking.mdc
+5-5Lines changed: 5 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@ When working on **`packages/react`** or comparing data-client to other React dat
16
16
17
17
- **Where it lives**: `examples/benchmark-react/`
18
18
- **How to run**: From repo root: `yarn build:benchmark-react`, then `yarn workspace example-benchmark-react preview &` and in another terminal `cd examples/benchmark-react && yarn bench`
19
-
- **What it measures**: Browser-based mount/update duration, ref-stability counts, sorted-view (Query memoization), optional memory (heap delta), startup metrics (FCP/TBT), and React Profiler commit times. Compares data-client, TanStack Query, SWR, and a plain React baseline.
19
+
- **What it measures**: Browser-based init/update duration, ref-stability counts, sorted-view (Query memoization), optional memory (heap delta), startup metrics (FCP/TBT), and React Profiler commit times. Compares data-client, TanStack Query, SWR, and a plain React baseline.
20
20
- **CI**: `.github/workflows/benchmark-react.yml` runs on changes to `packages/react/src/**`, `packages/core/src/**`, `packages/endpoint/src/schemas/**`, `packages/normalizr/src/**`, or `examples/benchmark-react/**` and reports via `rhysd/github-action-benchmark` (customSmallerIsBetter). CI runs **data-client only** (hot-path scenarios) to track regressions; competitor libraries (TanStack Query, SWR, baseline) are for local comparison only.
21
21
- **Report viewer**: Open `examples/benchmark-react/bench/report-viewer.html` in a browser and paste `react-bench-output.json` to view a comparison table and charts. Toggle "React commit" and "Trace" filters. Use "Load history" for time-series.
22
22
@@ -26,8 +26,8 @@ See `@examples/benchmark-react/README.md` for methodology, adding a new library,
26
26
27
27
Use this mapping when deciding which React benchmark scenarios are relevant to a change:
Copy file name to clipboardExpand all lines: examples/benchmark-react/README.md
+50-14Lines changed: 50 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,14 +11,14 @@ The repo has two benchmark suites:
11
11
12
12
## Methodology
13
13
14
-
-**What we measure:** Wall-clock time from triggering an action (e.g. `mount(100)` or `updateAuthor('author-0')`) until the harness sets `data-bench-complete` (after two `requestAnimationFrame` callbacks). Optionally we also record React Profiler commit duration and, with `BENCH_TRACE=true`, Chrome trace duration.
14
+
-**What we measure:** Wall-clock time from triggering an action (e.g. `init(100)` or `updateAuthor('author-0')`) until the harness sets `data-bench-complete` (after two `requestAnimationFrame` callbacks). Optionally we also record React Profiler commit duration and, with `BENCH_TRACE=true`, Chrome trace duration.
15
15
-**Why:** Normalized caching should show wins on shared-entity updates (one store write, many components update), ref stability (fewer new object references), and derived-view memoization (`Query` schema avoids re-sorting when entities haven't changed). See [js-framework-benchmark "How the duration is measured"](https://github.com/krausest/js-framework-benchmark/wiki/How-the-duration-is-measured) for a similar timeline-based approach.
16
16
-**Statistical:** Warmup runs are discarded; we report median and 95% CI. Libraries are interleaved per round to reduce environmental variance.
17
-
-**No CPU throttling:** Runs at native speed with more samples (3 warmup + 30 measurement locally, 15 in CI) for statistical significance rather than artificial slowdown.
17
+
-**No CPU throttling:** Runs at native speed with more samples for statistical significance rather than artificial slowdown. Small (cheap) scenarios use 3 warmup + 15 measurement runs locally (10 in CI); large (expensive) scenarios use 1 warmup + 4 measurement runs.
18
18
19
19
## Scenario categories
20
20
21
-
-**Hot path (in CI, data-client only)** — JS-only: mount, update propagation, ref-stability, sorted-view, bulk-ingest. No simulated network. CI runs only `data-client` scenarios to track our own regressions; competitor libraries are benchmarked locally for comparison.
21
+
-**Hot path (in CI, data-client only)** — JS-only: init (fetch + render), update propagation, ref-stability, sorted-view. No simulated network. CI runs only `data-client` scenarios to track our own regressions; competitor libraries are benchmarked locally for comparison.
22
22
-**With network (local comparison)** — Same shared-author update but with simulated network delay (consistent ms per "request"). Used to compare overfetching: data-client needs one store update (1 × delay); non-normalized libs typically invalidate/refetch multiple queries (N × delay). **Not run in CI** — run locally with `yarn bench` (no `CI` env) to include these.
23
23
-**Memory (local only)** — Heap delta after repeated mount/unmount cycles.
24
24
-**Startup (local only)** — FCP and task duration via CDP `Performance.getMetrics`.
@@ -27,20 +27,19 @@ The repo has two benchmark suites:
27
27
28
28
**Hot path (CI)**
29
29
30
-
-**Mount** (`mount-100-items`, `mount-500-items`) — Time to mount 100 or 500 item rows (unit: ms).
30
+
-**Init** (`init-100`, `init-500`) — Time to show a ListView component that auto-fetches 100 or 500 items from the list endpoint, then renders (unit: ms). Exercises the full fetch + normalization + render pipeline.
31
31
-**Update single entity** (`update-single-entity`) — Time to update one item and propagate to the UI (unit: ms).
32
32
-**Update shared author** (`update-shared-author-duration`) — 100 components, shared authors; update one author. Measures time to propagate (unit: ms). Normalized cache: one store update, all views of that author update.
33
33
-**Update shared author (scaling)** (`update-shared-author-500-mounted`, `update-shared-author-1000-mounted`) — Same update with 500/1000 mounted components to test subscriber scaling.
34
34
-**Ref-stability** (`ref-stability-item-changed`, `ref-stability-author-changed`) — Count of components that received a **new** object reference after an update (unit: count; smaller is better). Normalization keeps referential equality for unchanged entities.
35
35
-**Sorted view mount** (`sorted-view-mount-500`) — Mount 500 items through a sorted/derived view. data-client uses `useQuery(sortedItemsQuery)` with `Query` schema memoization; competitors use `useMemo` + sort.
36
36
-**Sorted view update** (`sorted-view-update-entity`) — After mounting a sorted view, update one entity. data-client's `Query` memoization avoids re-sorting when sort keys are unchanged.
37
-
-**Bulk ingest** (`bulk-ingest-500`) — Generate fresh data at runtime, ingest into cache, and render. Exercises the full normalization pipeline.
38
37
-**Optimistic update** (`optimistic-update`) — data-client only; applies an optimistic mutation via `getOptimisticResponse`.
39
38
-**Invalidate and resolve** (`invalidate-and-resolve`) — data-client only; invalidates a cached endpoint and immediately re-resolves. Measures Suspense boundary round-trip.
40
39
41
40
**With network (local comparison)**
42
41
43
-
-**Update shared author with network** (`update-shared-author-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." data-client uses 1 request; other libs use N requests (one per affected item query) to model overfetching.
42
+
-**Update shared author with network** (`update-shared-author-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." data-client propagates via normalization (no extra request); other libs invalidate/refetch the list endpoint.
44
43
45
44
**Memory (local only)**
46
45
@@ -57,19 +56,18 @@ These are approximate values to help calibrate expectations. Exact numbers vary
|`sorted-view-update-entity`| Fast (Query memoization skips re-sort) | Re-sorts on every item change | Re-sorts on every item change | Re-sorts on every item change |
Regressions >5% on stable scenarios or >15% on volatile scenarios are worth investigating.
@@ -84,7 +82,7 @@ Regressions >5% on stable scenarios or >15% on volatile scenarios are worth inve
84
82
## Adding a new library
85
83
86
84
1. Add a new app under `src/<lib>/index.tsx` (e.g. `src/urql/index.tsx`).
87
-
2. Implement the `BenchAPI` interface on `window.__BENCH__`: `mount`, `updateEntity`, `updateAuthor`, `unmountAll`, `getRenderedCount`, `captureRefSnapshot`, `getRefStabilityReport`, and optionally `mountUnmountCycle`, `bulkIngest`, `mountSortedView`. Use the shared presentational `ItemRow` from `@shared/components` and fixtures from `@shared/data`.
85
+
2. Implement the `BenchAPI` interface on `window.__BENCH__`: `init`, `updateEntity`, `updateAuthor`, `unmountAll`, `getRenderedCount`, `captureRefSnapshot`, `getRefStabilityReport`, and optionally `mountUnmountCycle`, `mountSortedView`. Use the shared presentational `ItemsRow` from `@shared/components` and fixtures from `@shared/data`. The harness (`useBenchState`) provides default `init`, `unmountAll`, `mountUnmountCycle`, `getRenderedCount`, and ref-stability methods; libraries only need to supply `updateEntity`, `updateAuthor`, and any overrides.
88
86
3. Add the library to `LIBRARIES` in `bench/scenarios.ts`.
89
87
4. Add a webpack entry in `webpack.config.cjs` for the new app and an `HtmlWebpackPlugin` entry so the app is served at `/<lib>/`.
90
88
5. Add the dependency to `package.json` and run `yarn install`.
@@ -133,6 +131,44 @@ Regressions >5% on stable scenarios or >15% on volatile scenarios are worth inve
133
131
-`BENCH_PORT=<port>` — port for `preview` server and bench runner (default `5173`)
134
132
-`BENCH_BASE_URL=<url>` — full base URL override (takes precedence over `BENCH_PORT`)
135
133
134
+
4.**Filtering scenarios**
135
+
136
+
The runner supports CLI flags (with env var fallbacks) to select a subset of scenarios:
When running all scenarios (`yarn bench`), each group runs with its own warmup/measurement count. Use `--size` to run only one group.
171
+
136
172
## Output
137
173
138
174
The runner prints a JSON array in `customSmallerIsBetter` format (name, unit, value, range) to stdout. In CI this is written to `react-bench-output.json` and sent to the benchmark action.
0 commit comments