Skip to content

[DRAFT] Add prototype signals+path-tracking implementation#2318

Draft
markerikson wants to merge 24 commits into
masterfrom
feature/selector-perf-prototype-02-path-signals
Draft

[DRAFT] Add prototype signals+path-tracking implementation#2318
markerikson wants to merge 24 commits into
masterfrom
feature/selector-perf-prototype-02-path-signals

Conversation

@markerikson

@markerikson markerikson commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This PR:

  • Adds a pair of signals-based alternative Provider/selector implementations, SignalProvider and useSignalSelector, designed to optimize the performance of subscriber notifications and selectors in larger apps (at the expense of larger bundle size)
    • adds a boatload of tests (of somewhat unknown quality)

Background

For the last several years I have been convinced that there is some way I can use signals and proxies to improve the subscriber+notification performance in large React-Redux apps.

Redux is a simple event emitter, with O(n) subscriber behavior. Every dispatched action triggers a loop over all subscriber callbacks, which in turn normally call getState() and a selector to determine if that useSelector or connect needs to re-render. React-Redux moves some of the tracking to be internal to itself and optimizes some subscription aspects, but it's fundamentally still calling O(n) callbacks every time.

As with React, this works fine for most apps, but breaks down at large scales. I've talked with teams that had 20,000 connected components, and at that scale just running the callbacks themselves is a major perf issue.

Proxies allow tracking access to nested fields. Signals allow automatically deriving dependencies. More magic, but signals libs automatically only update the dependencies that actually rely on a given updated value.

We can't and won't change the semantics of Redux in terms of immutable updates, subscribing to the store, etc. But, we can try to leverage some of these techniques inside of React-Redux, invisibly to the end user.

Previous Efforts

I've previously tried a couple different variations on this concept:

Neither got very far. They were mostly 1-day efforts that I never got back to. I think the autotracking PR sorta ran but already showed broken cases around field accesses.

Implementation Approach

This attempt pulls together a few different concepts from other libraries, as well as some seemingly new and unique approaches.

It uses a mixture of key path tracking ( "state.nested.field") and signals.

When a useSignalSelector hook mounts, it runs the actual selector (which as always could be a plain function, Reselect, or some other selector lib). That accesses fields in the state as usual.

However, the "state" the selector reads has been wrapped in a proxy. We track the accessed fields, and the terminal reads get turned into a signal for that path. The reads then set up standard signal dependencies, so that we effectively know which state paths this component depends on.

We have a root proxy and previous state value in SignalProvider. After an action is dispatched, we then do a diff between the old and new state, and calculate which state paths got changed. For those changed values, we call signal.set(newValue) internally. The updated signals then trigger the effect()s inside of useSignalSelector, which in turn trigger notifications of React.

There's a bunch of other internal implementation details I can write up later (like optimizing array updates via an entity-adapter-like ID tracking table, and reusing the same array method overrides approach I used on Immer), but that's the core idea.

Overall, the tradeoffs are:

  • The cost to mount a given component is higher because there's more subscriptions and values to set up
  • There is an up-front cost for every dispatch to do the root state reconciliation. That grows based on the size of the state + number of state paths being accessed.
  • We do assume that selectors are going to be consistent in which state paths they read.
  • If we can skip enough callbacks + selectors and that skipped cost is more than the time we spent doing the reconciliation, then this becomes a performance win
  • This definitely has a larger bundle size than plain useSelector. I haven't actually measured how big yet :) I'd assume on the order of 5-10K minified.

Usage

There's two new exports, SignalProvider and useSignalSelector. They ought to both be drop-in replacements for Provider and useSelector. Swap in SignalProvider, which passes down the existing context values + the signal data. Then call useSignalSelector in your components, same semantics, no user-visible difference.

Status and Notes

Status

Alpha-level, but it appears to be stable, is passing the new test suite so far, and is a significant improvement in my benchmarks suite (see below).

Haven't tried this in a real app yet, but the benchmarks setup is a suite of small example apps. So, between that and the unit tests, I'm pretty confident this runs in the cases I've tried so far.

Development Process

Getting this out up-front. The implementation here is 100% AI-written.

To be clear, this is engineered, not vibe-coded. I used my own standard "human-in-the-loop" AI engineering workflow. My ideas, fleshed out and implemented, over the course of a couple weeks. I reviewed all the lib code before each commit. (Maybe not all the tests :) )

I was also able to use AI to heavily revamp my longstanding React-Redux benchmarks repo: updating the build system, reviewing the existing scenarios, deleting dead ones, implementing better scenarios that provided better coverage of different use cases, and completely reworking what metrics I'm capturing so they're a lot more meaningful.

This is a POC, but it's code that I very intentionally drove the development of. I feel very confident that the current implementation actually runs as expected and implements the approaches I've worked through. I wouldn't even PR this unless I felt it was sufficiently solid enough to be public and tried out in real apps.

Performance

So, is this actually any better? :) (you would hope so... otherwise why would I even have taken the time to file this PR and write up this explanation?)

Based on my heavily revised and updated React-Redux benchmarks suite:

YES! This branch appears to be 20-40% faster in total scripting time per scenario than the current 9.x release

Here's a summary:

image

And the per-scenario details from my benchmarks suite:

Performance benchmark numbers
deeptree-nested-hooks
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   2149 │ 6931 │    652 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   2269 │ 6510 │    531 │ 10001 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1626 │ 6077 │    549 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  96.1 │     0.3 │     1.3 │     563 │      1162 (0.59) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  53.6 │     0.2 │     1.1 │     583 │      1182 (0.85) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  62.0 │     0.2 │     1.0 │     581 │      1178 (0.30) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      1417 │     6 │          88 │      203 │  42 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      1261 │     2 │          91 │      158 │  34 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      1154 │     4 │         120 │      146 │  35 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   244.1 │  439.0 │      1164 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   202.3 │  806.0 │      1184 │    250.3 │ 477096 │    90.6 │ 476296 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   202.0 │  153.4 │      1182 │      0.0 │      0 │     0.0 │      0 │     146.9 │ 1182 │   27.6 │ 8296 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
derived-selectors
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1665 │ 1903 │     45 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1851 │ 2084 │     42 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1048 │ 1254 │     37 │ 10014 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  11.6 │     0.4 │     0.6 │     341 │       399 (1.10) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  11.8 │     0.5 │     0.9 │     330 │       399 (1.36) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  62.1 │     0.1 │     0.2 │     338 │       398 (1.90) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      1169 │     4 │          38 │       44 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      1203 │     4 │          56 │       84 │   8 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       215 │     1 │         455 │       20 │   5 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    81.0 │  355.8 │       400 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    86.6 │  456.5 │       400 │    361.9 │ 111026 │    30.3 │ 110826 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    83.6 │  670.5 │       399 │      0.0 │      0 │     0.0 │      0 │     667.2 │  399 │  679.5 │ 704  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
entity-list-array
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1078 │ 1394 │     93 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1974 │ 2279 │     91 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    685 │ 1000 │     90 │ 10012 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 108.5 │     0.1 │     0.7 │     385 │       384 (1.29) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 106.9 │     0.2 │     0.8 │     359 │       358 (3.96) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  61.2 │     0.1 │     0.4 │     394 │       394 (0.41) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬──────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │  app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 8.1.1                   │       456 │     5 │           5 │       22 │  418 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 9.2.0                   │       410 │     3 │          28 │       28 │ 1247 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 9.2.0-signals-selectors │       401 │     4 │          73 │       26 │   27 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴──────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    77.3 │  418.5 │       385 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    84.9 │ 1332.5 │       359 │   1331.6 │ 73263 │    20.7 │ 71173 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    82.9 │   79.5 │       396 │      0.0 │     0 │     0.0 │     0 │      77.1 │  396 │   70.5 │ 4566 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
entity-list
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    619 │  927 │     88 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │    810 │ 1161 │    109 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    676 │  999 │     92 │ 10003 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  27.4 │     0.1 │     0.3 │     397 │       395 (0.36) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  33.9 │     0.1 │     0.6 │     396 │       395 (0.72) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  45.3 │     0.2 │     0.5 │     396 │       395 (0.37) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       416 │     4 │          10 │       31 │   6 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       457 │     2 │          23 │       66 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       422 │     3 │          58 │       28 │  15 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    80.2 │   59.8 │       397 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    87.9 │  194.9 │       397 │     96.8 │ 87267 │    22.8 │ 85172 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    83.7 │   59.2 │       396 │      0.0 │     0 │     0.0 │     0 │      57.1 │  396 │   28.2 │ 4697 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
forms
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    890 │ 5982 │    323 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1029 │ 6039 │    326 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    835 │ 4605 │    244 │ 10004 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 222.8 │     0.2 │     0.3 │     331 │       331 (0.43) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 229.5 │     0.2 │     0.3 │     329 │       329 (0.85) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │ 226.6 │     0.2 │     0.3 │     337 │       337 (0.16) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       630 │    12 │          40 │        7 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       643 │     3 │          20 │       18 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       671 │    10 │          20 │       16 │  37 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    23.8 │  117.0 │       332 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    22.7 │  257.0 │       329 │     77.3 │ 165659 │    36.6 │ 165158 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    24.8 │   27.5 │       337 │      0.0 │      0 │     0.0 │      0 │      25.3 │  337 │    6.9 │ 838  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
many-components-many-slices-bugged
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   7410 │ 7572 │     84 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7800 │ 7946 │     77 │  9999 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   4739 │ 6456 │    890 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  42.9 │    14.3 │    25.8 │      50 │        48 (4.31) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  47.6 │    22.1 │    33.4 │      47 │        45 (8.82) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  56.1 │     0.6 │     1.0 │     748 │       747 (3.26) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      7379 │    42 │         154 │      168 │  36 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      7365 │    80 │         249 │      164 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2250 │    10 │         742 │     1543 │  10 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   144.1 │   75.7 │        52 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   131.4 │  311.3 │        49 │    196.2 │ 495000 │    98.8 │ 490000 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  1677.1 │  764.0 │       750 │      0.0 │      0 │     0.0 │      0 │     759.5 │  750 │   27.7 │ 5750 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
many-components-many-slices
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   4990 │ 6799 │    937 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7438 │ 9150 │    890 │ 10002 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   4898 │ 6737 │    956 │ 10004 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  42.6 │     0.6 │     1.0 │     750 │       748 (3.21) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  49.0 │     0.6 │     0.9 │     731 │       729 (7.29) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  60.9 │     0.7 │     1.1 │     747 │       745 (3.21) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2557 │    13 │         160 │     1608 │  12 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2310 │     7 │         198 │     1480 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2439 │     8 │         835 │     1421 │  11 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  1671.2 │  742.0 │       754 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  1444.4 │ 3907.5 │       734 │   1218.1 │ 3675734 │   676.9 │ 3670734 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  1594.0 │  810.3 │       750 │      0.0 │       0 │     0.0 │       0 │     804.9 │  750 │   30.9 │ 5750 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴──────┘
many-components-same-slice
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   6715 │ 7666 │    676 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7138 │ 8008 │    605 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   6846 │ 7790 │    661 │ 10003 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  32.4 │    12.7 │    20.3 │      45 │        43 (1.23) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  37.2 │    17.9 │    24.6 │      45 │        43 (5.83) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  40.3 │    10.3 │    17.5 │      46 │        44 (5.00) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      6979 │    15 │         158 │       20 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      6993 │    16 │         237 │       23 │  15 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      6956 │    21 │         181 │       13 │  18 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬────────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#   │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 8.1.1                   │     4.5 │   53.9 │        47 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0      │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 9.2.0                   │     4.7 │  271.7 │        47 │    100.4 │ 475000 │    88.4 │ 470000 │       0.0 │    0 │    0.0 │ 0      │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 9.2.0-signals-selectors │     5.5 │  232.6 │        48 │      0.0 │      0 │     0.0 │      0 │     231.7 │   48 │   88.1 │ 245000 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴────────┘
multi-selector-component
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   3309 │ 4575 │    609 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   4441 │ 5590 │    551 │ 10014 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   3054 │ 4278 │    598 │ 10012 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  25.1 │     4.4 │    10.9 │     131 │       315 (1.41) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  30.2 │     6.9 │    18.0 │     124 │       289 (5.08) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  41.7 │     4.4 │    10.9 │     133 │       317 (0.49) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2717 │    19 │         222 │       38 │  20 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2514 │    12 │         323 │       38 │  39 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2639 │    12 │         171 │        9 │  48 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    33.3 │  408.2 │       316 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    34.3 │ 1432.1 │       289 │    378.0 │ 1404248 │   359.4 │ 1400248 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    36.5 │  116.3 │       317 │      0.0 │       0 │     0.0 │       0 │     114.5 │  317 │   57.1 │ 71065 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴───────┘
one-component-many-slices
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   6337 │ 6603 │     50 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   6314 │ 6574 │     50 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   9638 │ 9845 │     49 │ 10006 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │   2.1 │     0.0 │     0.1 │     770 │       769 (7.78) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │   2.1 │     0.0 │     0.1 │     770 │       769 (7.74) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │   2.5 │     0.0 │     0.1 │     752 │      750 (12.43) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       300 │     1 │          17 │     5913 │  24 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       283 │     2 │          21 │     5845 │  13 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       326 │     7 │        3211 │     6063 │  13 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬──────┬─────────┬──────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │ Sel# │ EqCheck │  Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  5995.3 │   22.7 │       773 │      0.0 │    0 │     0.0 │    0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  5954.2 │   27.5 │       772 │      1.6 │ 1545 │     0.6 │ 1544 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  6123.6 │ 3267.0 │       755 │      0.0 │    0 │     0.0 │    0 │    3263.0 │  755 │   14.8 │ 756  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴──────┴─────────┴──────┴───────────┴──────┴────────┴──────┘
rapid-dispatch
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    806 │ 1077 │     93 │ 10007 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1187 │ 1456 │     90 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    867 │ 1126 │     91 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  11.4 │     0.5 │     1.1 │     126 │       619 (0.13) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  13.0 │     0.7 │     2.0 │     131 │       617 (0.64) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  16.8 │     0.5 │     1.3 │     126 │       595 (0.15) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       697 │     3 │          13 │       19 │   6 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       756 │     1 │          34 │       33 │  10 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       728 │     1 │          32 │       15 │   7 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    37.9 │   43.7 │       628 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    47.8 │  350.9 │       626 │     91.8 │ 332109 │    74.4 │ 331609 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    37.9 │   49.9 │       604 │      0.0 │      0 │     0.0 │      0 │      48.4 │  604 │   22.9 │ 19395 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴───────┘
rtkq-separate-queries
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    880 │ 2454 │     63 │ 10009 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1022 │ 2715 │     64 │ 10007 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    785 │ 2173 │     55 │ 10010 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  22.4 │     0.7 │     2.8 │     205 │      2512 (0.44) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  22.4 │     0.8 │     3.1 │     212 │      2705 (0.46) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  24.7 │     1.3 │     3.8 │     172 │      2209 (0.45) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       426 │     5 │          16 │     1171 │  16 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       428 │     5 │          30 │     1343 │  15 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       374 │     1 │         108 │      981 │  15 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  1099.0 │    3.9 │      2514 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  1217.3 │    5.2 │      2707 │    255.1 │ 49569 │    14.7 │ 49369 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   977.9 │    1.9 │      2210 │      0.0 │     0 │     0.0 │     0 │     116.2 │  234 │   84.8 │ 3385 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
selective-update
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   3152 │ 4278 │    552 │ 10012 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   3968 │ 5052 │    521 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   3095 │ 4225 │    555 │ 10000 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  22.3 │     0.7 │     1.1 │     397 │       396 (0.62) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  25.3 │     0.9 │     1.3 │     397 │       396 (2.43) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  28.2 │     0.6 │     1.0 │     396 │       395 (0.48) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2774 │     6 │         112 │       24 │  18 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2824 │     8 │         150 │       24 │  30 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2817 │     6 │          72 │        6 │  12 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    28.1 │  216.6 │       398 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    33.5 │  932.5 │       398 │    263.6 │ 877600 │   202.3 │ 875600 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    28.2 │  157.4 │       397 │      0.0 │      0 │     0.0 │      0 │     155.1 │  397 │   73.7 │ 81400 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴───────┘
tree-view
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1789 │ 8784 │    303 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   3055 │ 9633 │    298 │ 10008 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1415 │ 7824 │    299 │ 10010 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 139.6 │     0.3 │     0.6 │     321 │       319 (3.22) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 147.4 │     0.3 │     0.9 │     293 │       292 (7.99) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │ 233.7 │     0.3 │     1.0 │     341 │       339 (1.62) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       778 │     9 │         218 │      360 │  42 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       788 │     9 │         196 │      318 │  52 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       617 │     9 │         187 │      358 │  61 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   412.0 │  620.2 │       321 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   356.1 │ 2002.7 │       295 │    543.5 │ 1494046 │   283.0 │ 1488952 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   433.1 │  118.5 │       341 │      0.0 │       0 │     0.0 │       0 │     116.5 │  341 │   30.5 │ 5449 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴──────┘

So, overall, it's meaningfully faster in 12 of the 14 benchmark scenarios, one is even, and the only case where it loses is an unrealistic "one component subscribed to hundreds of slices" scenario.

Next Steps?

At this point I think this is ready to be tried out in real-world apps. I'm sure there's edge cases that we aren't handling, just because real code is complex and we're doing tricky work here :) I'd love to know where this breaks, and if it appears to actually improve perf.

I need to do some testing to see how much this increases bundle size, as well as reviewing the overall test cases here and seeing what else we ought to cover.

For the record I do not plan on actually replacing the existing Provider and useSelector implementations. This is an alternative, not a replacement, primarily aimed at apps with large codebases and many subscriptions. I might ship an alternate entry point that would swap to the signal versions, and if you want to use them everywhere you could alias something like "react-redux/signals" as the import in your bundler setup.

CI Builds

As usual, see the CodeSandbox CI link below for the latest commit preview build.

Current one is:

# yarn 1
yarn add https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux
# yarn 2, 3
yarn add react-redux@https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux/_pkg.tgz
# npm
npm i https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux

@codesandbox-ci

codesandbox-ci Bot commented Jun 9, 2026

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Size Change: +21.1 kB (+61.14%) 🆘

Total Size: 55.6 kB

📦 View Changed
Filename Size Change
dist/cjs/react-redux.development.cjs 13.9 kB +4.92 kB (+54.7%) 🆘
dist/cjs/react-redux.production.min.cjs 7.13 kB +3.13 kB (+78.37%) 🆘
dist/react-redux.browser.mjs 6.86 kB +3.16 kB (+85.26%) 🆘
dist/react-redux.legacy-esm.js 13.7 kB +4.94 kB (+56.55%) 🆘
dist/react-redux.mjs 13.2 kB +4.91 kB (+58.89%) 🆘
dist/rsc.mjs 592 B +20 B (+3.5%)
ℹ️ View Unchanged
Filename Size
dist/cjs/index.js 140 B

compressed-size-action

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 49f63bc to dda99e1 Compare June 9, 2026 04:29
@GabbeV

GabbeV commented Jun 9, 2026

Copy link
Copy Markdown

Saw you working on this from your post on bluesky and got interested in how it works so had a look.

I think I found a limitation to this approach that I think is fundamentally unavoidable. Maybe you've figured it out already and intend to document it as a gotcha.

If the logic in the selector relies on object identity then the proxies can't really catch that dependency as proxies can't see Object.is or === etc. You can't assume a object is used just because it is read either as that would make everything depend on the root object that changes all the time defeating the entire optimization.

https://codesandbox.io/p/sandbox/festive-meadow-q7rk68?file=%2Fsrc%2FApp.tsx%3A7%2C15

@dai-shi

dai-shi commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Nice. Avoiding O(n) subscription is great, because proxy-memoize approach is still O(n).
If we were okay with leaking proxies to render functions, useTrackedState or useSignalState might be possible too.
(BTW, I'm planning O(1) subscription (edit: well, it might be misleading. ref: pmndrs/valtio#1161 ) for Valtio v3.)

@markerikson

markerikson commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@GabbeV Having trouble opening that sandbox and not sure if it's network issues, CSB issues, or just isn't there. Could you post the relevant snippets? I'll try to repro separately in the meantime.

@dai-shi eeenteresting! would love to see what you have in mind for O(1) behavior!

@GabbeV

GabbeV commented Jun 10, 2026

Copy link
Copy Markdown

@markerikson
Here is the main part of the repro

import { configureStore } from "@reduxjs/toolkit";
import {
  SignalProvider,
  useSignalSelector,
  useSelector,
  useDispatch,
} from "react-redux";

const a = { id: "a" };
const b = { id: "b" };

const initialState: State = {
  items: [a, b],
  current: a,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "selectA":
      return { ...state, current: state.items[0] };

    case "selectB":
      return { ...state, current: state.items[1] };

    default:
      return state;
  }
}

const store = configureStore({ reducer });

function selectCurrentByReference(state) {
  return state.items.find((item) => item === state.current);
}

export default function App() {
  return (
    <SignalProvider store={store}>
      <AppInner />
    </SignalProvider>
  );
}

function AppInner() {
  const dispatch = useDispatch();

  return (
    <main>
      <button onClick={() => dispatch({ type: "selectA" })}>Select A</button>
      <button onClick={() => dispatch({ type: "selectB" })}>Select B</button>

      <SignalResult />
      <NormalResult />
    </main>
  );
}

function SignalResult() {
  const current = useSignalSelector(selectCurrentByReference);
  return <p>signal active: {current?.id ?? "none"}</p>;
}

function NormalResult() {
  const current = useSelector(selectCurrentByReference);
  return <p>normal active: {current?.id ?? "none"}</p>;
}

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 65ed737 to 5f96e70 Compare June 11, 2026 10:17
@markerikson

markerikson commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@GabbeV okay, did some research and work around object identity and edge cases.

I've added an unwrap() util that can be used to help with reference-based comparisons. However, the {current: someSharedReference} + array.find(item => item === current) comparison case is tricky enough that it seems like we can't cover it automatically. The options are either:

  • Auto-wrap all array values in proxies, which kills perf
  • Do a double-call on find() with and without wrapped values just in case the second call works
  • use unwrap() if it actually helps here

Generally, find() with a non-nested reference comparison is somewhat non-idiomatic in Redux. I think typically you'd be doing a item.id === selectedId check anyway. (Plus... if you're looking for an item by its reference, doesn't that mean you have it already? :) )

@GabbeV

GabbeV commented Jun 11, 2026

Copy link
Copy Markdown

@markerikson sounds like there are more trickiness there than I even realized. It for sure is unidiomatic redux to have logic around object identity at all so I don't think this is a deal breaker. It is just an example showing that non deterministic logic isn't the only thing preventing this from being a perfect drop in replacement without at least some disclaimers.

I'm not sure how you would get away with not wrapping each accessed element in an array find without missing a bunch of real dependencies in a case like useSignalSelector(s => a.find(x => x.done)?.name). Every item you scanned over could become done later so you do need dependency on each as well as dependency on new items getting added to the array. However using an array in my example was not part of the issue I was trying to show at all.

The issue I was getting at isn't even that === is always false, it is that === won't register a dependency. I think unwrap is kind of a nice solution to both of those issues though. However I think it needs to not only give access to the wrapped value but also register a dependency on the object changing. Imagine a selector like this useSignalSelector(s => s.a === s.b ? "true" : "false"). If a and b are objects the selector wouldn't get registered to rerun as they aren't "terminal values".

@markerikson

Copy link
Copy Markdown
Contributor Author

@GabbeV yeah, thanks for bringing up these edge cases!

I think I already had the useSignalSelector(s => s.a === s.b ? "true" : "false") case handled in this PR. We actually track both leaf values and their immediate parent, and that seems to cover that case.

As for the array methods: yeah, I had added the array method wrappers that delegate to the underlying array, specifically to avoid the overhead of creating proxies every time you iterate through an array even if you're just reading. Did some more thinking on this, and I just updated the PR to auto-track the array itself any time you call one of the methods. The key brainstorm I had here is that this whole PR isn't the entire state change detection method - it's a set of heuristics to figure out if it's safe to skip the callbacks and selectors we are pretty sure would be irrelevant because their dependencies haven't changed. So, we need to make sure we never miss an update, but it's okay if we still run some selectors that report "no change, don't re-render".

That means that always marking an array as being accessed and needing consideration ought to handle cases like array.find/filter(), without needing to create proxies for all the individual fields. The latest commit has tests for those cases.

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 18e22cc to 97379d5 Compare June 11, 2026 16:06
@GabbeV

GabbeV commented Jun 11, 2026

Copy link
Copy Markdown

@markerikson allowing false positive executions sounds like a smart approach for the arrays if wrapping each item is prohibitive performance wise. I'm wondering if maybe you could reuse proxies as an alternative solution if the performance issue is because of allocations? Not for different objects but for the same object between different selectors. Basically you save the proxy per object in a weakmap or on a hidden symbol property on the object it self.

Maybe I am missing something but I believe the issue I'm describing with useSignalSelector(s => s.a === s.b ? "true" : "false") is fundamentally unsupportable with proxies unless rewritten to something like useSignalSelector(s => unwrapAndTrack(s.a) === unwrapAndTrack(s.b) ? "true" : "false").

Here is a more complex example:

useSignalSelector(s => {
  const a = s.a
  const aFooBar = a.foo.bar
  const b = s.b
  const bFooBar = b.foo.bar
  
  if (a === b) return "same"
  if (aFooBar === bFooBar) return "similar"
  return "different"
})

The proxies can see that you are traversing down s.a.foo.bar and s.b.foo.bar but they can't possibly see that you are checking the identity between s.a and s.b as there is no proxy trap for equality. Imagine the object starting of as "same" but then some property unrelated to foo.bar changes on one of them so that they are no longer the "same" but still "similar".

if (unwrap(a) === unwrap(b)) return "same" would just solve that branch failing because of the unstable identity of proxy wrappers but does not cause the selector to rerun for object changes unless unwrap also causes it to subscribe to any change on the unwrapped value similar to your array solution.

@markerikson

Copy link
Copy Markdown
Contributor Author

@GabbeV I'm inclined to say this is a limitation. Obviously we want selector code to work out of the box. But especially with proxies, we can't cover literally every bit of JS syntax. As it is, we document the "zombie child" case for useSelector because it's a known limitation in how subscriptions work. My biggest goals here are cutting down on subscription overhead for large apps. It's opt-in at the user level by using useSignalSelector, so it's okay if there's some kind of edge case limitation.

@nstadigs

Copy link
Copy Markdown

Would this make supporting "async react"/concurrency easier or harder?

@markerikson

Copy link
Copy Markdown
Contributor Author

@nstadigs I'm not sure how it fits architecturally at this point.

One reason I'd held off trying to push this idea forward was that the useStore / "concurrent store" prototyping work was going on a few months ago, but as you've seen that appears to be stalled / dead atm. Given that, I figured it was worth at least seeing if I could get this concept to work and if there were perf benefits.

I'm not planning to ship this soon (would need a lot more benchmarking and real-world testing first), but big-picture I wouldn't want to hold off on this "just in case" the React team somehow decides to actually solve that use case. The combination of Meta layoffs, team shuffling, lack of response to the useStore PR, and apparent internal discussion of "is this even the right API design?" says to me that ain't happening for the foreseeable future, so I'm not going to sit around waiting for something to happen there first.

@adardesign

Copy link
Copy Markdown

♥️

@markerikson

Copy link
Copy Markdown
Contributor Author

FWIW the last commits I pushed that relate to tracking arrays appear to have lost most of the perf gains :( Debating options around correctness vs perf, but also running around and juggling time to work on this.

As things stand atm: I'd absolutely appreciate feedback on the correctness of the current implementation in real apps, as well as any more edge cases like the ones discussed above where it turns out this breaks or doesn't propagate updates as expected. As of the current commit I expect it's probably not much faster than useSelector, and I'm still looking into that, but getting feedback on how it runs overall would be helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants