Skip to content

perf(createSelector): stop allocating dev-check bookkeeping on every call - #773

Open
veksa wants to merge 1 commit into
reduxjs:masterfrom
veksa:perf/dev-mode-checks
Open

perf(createSelector): stop allocating dev-check bookkeeping on every call#773
veksa wants to merge 1 commit into
reduxjs:masterfrom
veksa:perf/dev-mode-checks

Conversation

@veksa

@veksa veksa commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The dev-mode checks live inside the hot path, and getDevModeChecksExecutionInfo builds four objects — a spread, a wrapper, and one per check — every time a selector recomputes its dependencies. Both checks default to 'once', so from the second recomputation onwards all four are allocated, read for two booleans, and thrown away.

This resolves the two frequencies in place and calls the check functions directly. getDevModeChecksExecutionInfo has no callers left, so it goes; the public DevModeChecksExecutionInfo type stays. Production is unaffected — the whole change sits inside the NODE_ENV !== 'production' guard.

One thing worth a look in review: I used hasOwnProperty rather than ??. The spread this replaces means devModeChecks: { identityFunctionCheck: undefined } silences the check — it's a present key overriding the default with a frequency that's neither 'once' nor 'always' — where ?? would fall back to the global setting and turn it back on. That edge wasn't covered, so there's a test for it now, and it does fail with ??. The common case has no overrides and reads the global directly, no hasOwnProperty call at all.

Measured with the harness from #770, dev mode (yarn bench:hot-path:dev), paired against master in one process, ns/call:

case before after
3 inputs, (state, props) 251.5 217.8 1.15x
1 input, (state, props) 203.7 179.4 1.14x
6 inputs, (state, props) 355.4 317.7 1.12x
same as second, argsMemoize: lruMemoize 89.9 80.7 1.11x
1 input, (state) 12.4 11.9 noise
nested (2 output selectors) 12.9 12.7 noise

Dev mode was costing 20–27% over production on the parametric cases; it's now roughly 12–20%. The last two rows are selectors called with the state alone — they hit the argument cache, so they don't recompute dependencies and there's nothing here to skip. Recomputation counts are identical throughout.

@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for reselect-docs canceled.

Name Link
🔨 Latest commit 36a7dce
🔍 Latest deploy log https://app.netlify.com/projects/reselect-docs/deploys/6a7599d0d2ad4800084729e1

@codesandbox-ci

codesandbox-ci Bot commented Aug 7, 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.

@markerikson

Copy link
Copy Markdown
Contributor

Nice! Will follow up on these as soon as I have some time. Thanks!

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.

2 participants