Skip to content

enhance(normalizr,endpoint): Reduce allocations in hot cache paths#3879

Merged
ntucker merged 3 commits intomasterfrom
perf/deopt-elimination-globalcache-collection
Apr 6, 2026
Merged

enhance(normalizr,endpoint): Reduce allocations in hot cache paths#3879
ntucker merged 3 commits intomasterfrom
perf/deopt-elimination-globalcache-collection

Conversation

@ntucker
Copy link
Copy Markdown
Collaborator

@ntucker ntucker commented Apr 6, 2026

Summary

  • Inline getCacheKey in GlobalCache.getEntity: Avoids eagerly creating both localCache and cycleCache Maps per entity type on every access. The cycleCache is now only created when actually entering the denormalize path (lazy initialization). Also replaces push(...spread) with an indexed for-loop when copying cached entity dependencies to avoid spread operator overhead.
  • Pre-create _removeSchema in Collection.CreateMover: Eliminates per-call Object.create in normalizeMove, removing a source of hidden class polymorphism that caused V8 "wrong call target" deoptimizations. The addSchema is also eliminated since this.merge is already the addMerge function.

These optimizations target the move-item and update-user-10000 React benchmark scenarios identified via --trace-opt --trace-deopt profiling.

Test plan

  • Existing normalizr, core, and react test suites pass
  • move-item and update-user-10000 React benchmarks show no regression

Made with Cursor


Note

Medium Risk
Moderate risk: changes touch core normalization/denormalization cache behavior and collection membership updates; intended semantics should be preserved but regressions would be subtle and performance-focused.

Overview
Reduces allocations in hot normalization/denormalization paths.

In Collection.CreateMover/normalizeMove, pre-creates and reuses a _removeSchema and stops creating per-call addSchema/removeSchema, reusing this for add merges while still using the prebuilt remove schema.

In GlobalCache.getEntity, lazily initializes the per-entity-type cycleCache only when entering the compute/denormalize branch, and replaces dependencies.push(...deps) with an indexed loop to avoid spread overhead.

Reviewed by Cursor Bugbot for commit 2a50525. Bugbot is set up for automated code reviews on this repo. Configure here.

Inline getCacheKey in GlobalCache to avoid eagerly creating both
localCache and cycleCache Maps per entity type. Replace push(...spread)
with an indexed for-loop when copying cached entity dependencies.

Pre-create _removeSchema in Collection.CreateMover so normalizeMove
no longer calls Object.create on every invocation, eliminating hidden
class polymorphism that caused V8 "wrong call target" deoptimizations.

Made-with: Cursor
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 6, 2026

⚠️ No Changeset found

Latest commit: 2a50525

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs-site Ignored Ignored Preview Apr 6, 2026 1:00pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Size Change: +29 B (+0.04%)

Total Size: 80.6 kB

📦 View Changed
Filename Size Change
examples/test-bundlesize/dist/rdcClient.js 10.5 kB +29 B (+0.28%)
ℹ️ View Unchanged
Filename Size Change
examples/test-bundlesize/dist/App.js 1.46 kB 0 B
examples/test-bundlesize/dist/polyfill.js 307 B 0 B
examples/test-bundlesize/dist/rdcEndpoint.js 8 kB -7 B (-0.09%)
examples/test-bundlesize/dist/react.js 59.7 kB 0 B
examples/test-bundlesize/dist/webpack-runtime.js 726 B 0 B

compressed-size-action

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark React

Details
Benchmark suite Current: 2a50525 Previous: 98a7831 Ratio
data-client: getlist-100 177.01 ops/s (± 4.6%) 177.01 ops/s (± 5.0%) 1
data-client: getlist-500 46.51 ops/s (± 5.7%) 48.47 ops/s (± 5.3%) 1.04
data-client: update-entity 476.19 ops/s (± 4.6%) 416.67 ops/s (± 6.3%) 0.88
data-client: update-user 454.55 ops/s (± 5.6%) 434.78 ops/s (± 5.7%) 0.96
data-client: getlist-500-sorted 51.28 ops/s (± 5.1%) 50.13 ops/s (± 7.4%) 0.98
data-client: update-entity-sorted 392.31 ops/s (± 6.5%) 384.62 ops/s (± 3.2%) 0.98
data-client: update-entity-multi-view 384.62 ops/s (± 8.4%) 322.58 ops/s (± 6.0%) 0.84
data-client: list-detail-switch-10 12.45 ops/s (± 8.1%) 11.87 ops/s (± 6.3%) 0.95
data-client: update-user-10000 101.02 ops/s (± 3.5%) 99.01 ops/s (± 4.8%) 0.98
data-client: invalidate-and-resolve 51.28 ops/s (± 4.8%) 50.76 ops/s (± 3.7%) 0.99
data-client: unshift-item 322.58 ops/s (± 0.0%) 333.33 ops/s (± 6.3%) 1.03
data-client: delete-item 454.55 ops/s (± 6.0%) 416.67 ops/s (± 0.0%) 0.92
data-client: move-item 263.16 ops/s (± 3.0%) 227.27 ops/s (± 3.3%) 0.86

This comment was automatically generated by workflow using github-action-benchmark.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.11%. Comparing base (98a7831) to head (2a50525).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3879   +/-   ##
=======================================
  Coverage   98.10%   98.11%           
=======================================
  Files         153      153           
  Lines        2906     2913    +7     
  Branches      565      565           
=======================================
+ Hits         2851     2858    +7     
  Misses         11       11           
  Partials       44       44           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 2a50525 Previous: 98a7831 Ratio
normalizeLong 448 ops/sec (±1.23%) 445 ops/sec (±2.41%) 0.99
normalizeLong Values 416 ops/sec (±0.16%) 415 ops/sec (±0.25%) 1.00
denormalizeLong 301 ops/sec (±2.75%) 300 ops/sec (±2.18%) 1.00
denormalizeLong Values 269 ops/sec (±2.32%) 274 ops/sec (±2.20%) 1.02
denormalizeLong donotcache 1021 ops/sec (±0.14%) 1027 ops/sec (±0.40%) 1.01
denormalizeLong Values donotcache 760 ops/sec (±0.18%) 761 ops/sec (±0.31%) 1.00
denormalizeShort donotcache 500x 1570 ops/sec (±0.64%) 1584 ops/sec (±0.61%) 1.01
denormalizeShort 500x 849 ops/sec (±2.24%) 864 ops/sec (±2.10%) 1.02
denormalizeShort 500x withCache 7526 ops/sec (±0.13%) 6266 ops/sec (±0.11%) 0.83
queryShort 500x withCache 2969 ops/sec (±0.11%) 2799 ops/sec (±0.19%) 0.94
buildQueryKey All 55104 ops/sec (±0.21%) 55711 ops/sec (±0.17%) 1.01
query All withCache 6033 ops/sec (±0.19%) 6132 ops/sec (±0.30%) 1.02
denormalizeLong with mixin Entity 284 ops/sec (±2.35%) 285 ops/sec (±2.01%) 1.00
denormalizeLong withCache 8158 ops/sec (±0.20%) 7880 ops/sec (±0.25%) 0.97
denormalizeLong Values withCache 5101 ops/sec (±0.11%) 5100 ops/sec (±0.12%) 1.00
denormalizeLong All withCache 5844 ops/sec (±0.13%) 5871 ops/sec (±0.67%) 1.00
denormalizeLong Query-sorted withCache 6057 ops/sec (±0.18%) 6159 ops/sec (±0.26%) 1.02
denormalizeLongAndShort withEntityCacheOnly 1864 ops/sec (±0.19%) 1786 ops/sec (±0.23%) 0.96
denormalize bidirectional 50 5808 ops/sec (±1.85%) 5943 ops/sec (±1.89%) 1.02
denormalize bidirectional 50 donotcache 40596 ops/sec (±0.97%) 40836 ops/sec (±0.64%) 1.01
getResponse 4576 ops/sec (±0.60%) 4791 ops/sec (±0.53%) 1.05
getResponse (null) 11060843 ops/sec (±0.94%) 10513376 ops/sec (±0.93%) 0.95
getResponse (clear cache) 267 ops/sec (±1.89%) 274 ops/sec (±1.87%) 1.03
getSmallResponse 3619 ops/sec (±0.08%) 3370 ops/sec (±0.07%) 0.93
getSmallInferredResponse 2763 ops/sec (±0.08%) 2572 ops/sec (±0.11%) 0.93
getResponse Collection 4464 ops/sec (±0.35%) 4588 ops/sec (±0.42%) 1.03
get Collection 4583 ops/sec (±0.28%) 4708 ops/sec (±0.26%) 1.03
get Query-sorted 5202 ops/sec (±0.15%) 5380 ops/sec (±0.25%) 1.03
setLong 448 ops/sec (±0.41%) 462 ops/sec (±0.16%) 1.03
setLongWithMerge 249 ops/sec (±0.49%) 257 ops/sec (±0.21%) 1.03
setLongWithSimpleMerge 271 ops/sec (±0.23%) 274 ops/sec (±0.15%) 1.01
setSmallResponse 500x 928 ops/sec (±0.14%) 940 ops/sec (±0.10%) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@ntucker ntucker merged commit f57e925 into master Apr 6, 2026
27 checks passed
@ntucker ntucker deleted the perf/deopt-elimination-globalcache-collection branch April 6, 2026 13:07
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.

1 participant