demo: Add benchmark-react with normalization and ref-stability scenarios#3783
demo: Add benchmark-react with normalization and ref-stability scenarios#3783
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
af004f6 to
a2cb25d
Compare
|
Size Change: 0 B Total Size: 80.5 kB ℹ️ View Unchanged
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3783 +/- ##
=======================================
Coverage 98.05% 98.05%
=======================================
Files 151 151
Lines 2834 2834
Branches 555 555
=======================================
Hits 2779 2779
Misses 11 11
Partials 44 44 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Browser benchmark comparing @data-client/react (Playwright, customSmallerIsBetter). - Scenarios: mount, update entity/author, ref-stability (item/author ref counts). - Hot-path (CI) vs with-network (local): simulated delay for overfetch comparison. - CI workflow runs hot-path only; reports to rhysd/github-action-benchmark. Made-with: Cursor
d8d904f to
98de6fa
Compare
| prev ? { ...prev, author: newAuthor } : prev, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
SWR/TanStack updateAuthor iterates all 1000 fixture items
Low Severity
The updateAuthor callbacks in both SWR and TanStack Query implementations loop over all 1000 FIXTURE_ITEMS to find items matching the author, calling mutate/setQueryData for ~50 items (1000 items / 20 authors). However, only 100 items are typically mounted, so ~45 of those cache writes target unmounted items. This unnecessary work inflates the update-shared-author-duration measurement for these libraries compared to what a real-world implementation would do.
Additional Locations (1)
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: 05e8319 | Previous: 3b85c82 | Ratio |
|---|---|---|---|
normalizeLong |
441 ops/sec (±0.95%) |
458 ops/sec (±0.70%) |
1.04 |
normalizeLong Values |
404 ops/sec (±0.35%) |
414 ops/sec (±0.45%) |
1.02 |
denormalizeLong |
282 ops/sec (±2.42%) |
299 ops/sec (±2.23%) |
1.06 |
denormalizeLong Values |
261 ops/sec (±2.07%) |
267 ops/sec (±2.69%) |
1.02 |
denormalizeLong donotcache |
1041 ops/sec (±0.16%) |
1052 ops/sec (±0.15%) |
1.01 |
denormalizeLong Values donotcache |
766 ops/sec (±0.22%) |
772 ops/sec (±0.50%) |
1.01 |
denormalizeShort donotcache 500x |
1591 ops/sec (±0.15%) |
1434 ops/sec (±0.15%) |
0.90 |
denormalizeShort 500x |
854 ops/sec (±2.24%) |
792 ops/sec (±2.08%) |
0.93 |
denormalizeShort 500x withCache |
6211 ops/sec (±0.11%) |
5211 ops/sec (±0.57%) |
0.84 |
queryShort 500x withCache |
2715 ops/sec (±0.12%) |
2648 ops/sec (±0.22%) |
0.98 |
buildQueryKey All |
54065 ops/sec (±0.47%) |
56000 ops/sec (±0.51%) |
1.04 |
query All withCache |
7106 ops/sec (±0.16%) |
6060 ops/sec (±0.49%) |
0.85 |
denormalizeLong with mixin Entity |
272 ops/sec (±2.28%) |
284 ops/sec (±2.20%) |
1.04 |
denormalizeLong withCache |
5949 ops/sec (±0.12%) |
7079 ops/sec (±0.31%) |
1.19 |
denormalizeLong Values withCache |
5057 ops/sec (±0.29%) |
4556 ops/sec (±0.19%) |
0.90 |
denormalizeLong All withCache |
7014 ops/sec (±0.32%) |
4980 ops/sec (±0.14%) |
0.71 |
denormalizeLong Query-sorted withCache |
7110 ops/sec (±0.22%) |
5793 ops/sec (±0.13%) |
0.81 |
denormalizeLongAndShort withEntityCacheOnly |
1666 ops/sec (±0.42%) |
1534 ops/sec (±0.48%) |
0.92 |
getResponse |
4602 ops/sec (±0.56%) |
3652 ops/sec (±0.58%) |
0.79 |
getResponse (null) |
9986046 ops/sec (±0.93%) |
9869619 ops/sec (±0.70%) |
0.99 |
getResponse (clear cache) |
258 ops/sec (±2.22%) |
276 ops/sec (±2.36%) |
1.07 |
getSmallResponse |
3385 ops/sec (±0.11%) |
3347 ops/sec (±0.47%) |
0.99 |
getSmallInferredResponse |
2499 ops/sec (±0.12%) |
2566 ops/sec (±0.23%) |
1.03 |
getResponse Collection |
4570 ops/sec (±0.70%) |
3722 ops/sec (±0.09%) |
0.81 |
get Collection |
4556 ops/sec (±0.23%) |
3330 ops/sec (±0.18%) |
0.73 |
get Query-sorted |
5170 ops/sec (±0.40%) |
4875 ops/sec (±0.48%) |
0.94 |
setLong |
438 ops/sec (±0.38%) |
462 ops/sec (±0.25%) |
1.05 |
setLongWithMerge |
255 ops/sec (±0.41%) |
260 ops/sec (±0.23%) |
1.02 |
setLongWithSimpleMerge |
267 ops/sec (±0.39%) |
278 ops/sec (±0.45%) |
1.04 |
setSmallResponse 500x |
947 ops/sec (±0.10%) |
922 ops/sec (±0.49%) |
0.97 |
This comment was automatically generated by workflow using github-action-benchmark.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
|
||
| const rootEl = document.getElementById('root') ?? document.body; | ||
| createRoot(rootEl).render( | ||
| <SWRConfig value={{ provider: () => cache as any }}> |
There was a problem hiding this comment.
SWR app missing revalidation disable skews benchmark results
Medium Severity
The SWR app's SWRConfig doesn't disable automatic revalidation, unlike the TanStack Query app which sets staleTime: Infinity and gcTime: Infinity. SWR's default behavior triggers a background fetch on every useSWR mount. Since fetcher always rejects, each of the 100–1000 mounted ItemView components triggers an unnecessary Promise.reject, error handling, and potential extra re-render. This systematically inflates SWR's measured durations compared to the other libraries, producing misleading local comparison results.


Motivation
We need a browser-based benchmark for
@data-client/reactthat highlights the performance advantages of normalization and referential equality: when one entity is updated, only components that use that entity get new references; others keep the same object reference so React can skip rerenders. This enables comparison against other React data libraries (e.g. TanStack Query, SWR) and surfaces regressions in CI.Solution
examples/benchmark-react: Playwright-driven benchmark that runs a small React app with shared authors, measures mount/update duration viaperformance.measure(), and reports incustomSmallerIsBetterformat forrhysd/github-action-benchmark.data-clienttrack our regressions; competitor libraries (TanStack Query, SWR, baseline) are benchmarked locally for comparison only.yarn bench:run:compilerbuilds withbabel-plugin-react-compiler(via@anansi/babel-preset'sreactCompileroption) and labels results[compiler]for side-by-side comparison in the report viewer.Scenarios
Hot path (CI, data-client only)
Queryschema memoization vsuseMemosort)Local comparison only
Key design decisions
performance.measure()(JS-driven), React ProfileractualDuration(reconciliation), Chrome trace duration (full rendering pipeline)bench/report-viewer.html): filterable table (base/react-commit/trace), time-series charting via history file loadingScripts
yarn bench:runyarn bench:run:compiler[compiler]yarn build:compileryarn bench:compiler[compiler]label onlyOpen questions
N/A
Made with Cursor
Note
Medium Risk
Adds a sizable new benchmark app plus Playwright-based CI workflow changes, which can impact CI runtime/stability and benchmark reporting despite not affecting production library code paths.
Overview
Adds a new browser-driven benchmark suite
examples/benchmark-reactto measure React rendering performance and ref-stability across@data-client/reactand competitor libraries, including a Playwright runner, scenario definitions, and a local report viewer.Introduces a new GitHub Actions workflow
benchmark-react.ymlto run these benchmarks on relevant PRs/pushes (CI executes data-client-only hot-path scenarios) and updates the existing Node benchmark workflow with permissions/concurrency plus a cache-key fix. Also wires the new workspace +build:benchmark-reactscript into the repo, updates benchmark documentation/agent guidance, and refreshes playgroundRestEndpointeditor type definitions.Written by Cursor Bugbot for commit 05e8319. This will update automatically on new commits. Configure here.