Skip to content

perf(router-core): reduce replaceEqualDeep allocations - #8411

Merged
schiller-manuel merged 3 commits into
schiller-manuel-perf-replace-equal-deep-split-phasefrom
codex/replace-equal-deep-child-buffers
Sep 16, 2026
Merged

schiller-manuel merged 3 commits into
schiller-manuel-perf-replace-equal-deep-split-phasefrom
codex/replace-equal-deep-child-buffers

Conversation

@Sheraff

@Sheraff Sheraff commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

🎯 Changes

Stacked on #8389 (schiller-manuel-perf-replace-equal-deep-split-phase).

Fix structural-sharing cases where the previous value could retain removed symbols, a sparse array could pass validation when a hole and an extra key cancel out, or a built-in with an own null/undefined constructor could be treated as a plain object. Signed zero consistently prefers the previous value wherever strict equality matches. Prototype-based classification preserves inherited nullish-constructor compatibility.

replaceEqualDeep resolves object children into the private previous-key array. It returns prev when equal, reuses next when its children already contain every resolved value/reference, and otherwise creates a copy. Null-prototype mode controls newly constructed objects; either input can be reused with its original prototype. Arrays scan the equal prefix and reuse the validated next-key buffer for changed results. Neither input is mutated.

Getters, cross-realm behavior, ordinary own __proto__ copying, an own constructor: Object, non-enumerable array indices, and cyclic result semantics beyond the existing depth limit are outside the requested contract. Auxiliary array metadata, including symbols, remains an existing limitation.

Three-way CPU benchmark

Against #8389, #8411 takes 18.1% less time for a changed flat search, 32.7% less for successive ordinary search updates, 48.1% less for the two-call selector sequence, and 29.1–43.3% less for changed long primitive arrays. The costs are 13.1–32.3% more time for equal search scenarios, 50.2% more for equal empty objects, 39.3% more for equal arrays of objects, and 19.8% more for arrays of copied objects with one changed item. The fixed mixed fixture is +2.0%; it is one supplied scenario, not an estimate of production workload frequencies.

Against current main, equal/changed flat search takes 31.3%/38.2% less time, equal arrays of objects 32.6% less, and a long primitive array with the last item changed 34.5% less. The full table retains every regression, including output-consumption timings, and flags the constructor-key behavior mismatch.

Version Commit SHA-256 of complete production utils.ts
main 6954a370a76251cf933ede95562c968adc4e847b 6c269b342a577ea1629c7a9412f1f1345c7a7d81fda9b8e7dc260276b5015361
#8389 068750d2fba96dd49841e93dc8b3e3961db52181 60b2e03a00135107f9df0f93289cf2ba93f16ae9b1f5a8b3a8cd71860a6cea95
#8411 ce00e84b794626a3e9c548ccd94d3d4738724345 aaced5f47a2bcb3107244563407fcbfb83bda205c7c69226b18487f43000fa55

All 60 scenarios from packages/router-core/tests/replace-equal-deep.bench.ts at #8411 were run in one Vitest invocation: each original scenario is one describe, containing bench('main'), bench('#8389'), and bench('#8411'). Vitest runs the three entries sequentially in the same worker/process (main, #8389, #8411 order per scenario); they do not compete concurrently for CPU. The original 1,000-iteration callback bodies, selector sequences, mixed fixture and array consumers were retained. Each callback has its own version-specific module and source/helper imports. The full source snapshots are byte-for-byte copies from the pinned commits above.

Node v25.8.1, V8 14.1.146.11-node.21, Vitest 4.1.4, Tinybench 2.9.0, Apple M3 Max, macOS 15.7.9 arm64; existing jsdom benchmark environment. One worker; no parallel test files. Each bench received 250 ms warmup, at least 1,000 ms measurement and at least 50 samples. No concurrent agent benchmark/build/test load was run. Timing retains normal GC and scheduling effects.

Numbers below are mean nanoseconds per original inner-loop action, lower is better. Deltas are time changes, so negative is faster. Most actions are one function call. The selector-followed-by-same-reference scenario is two calls per action; consumption scenarios are one complete array sum/reduce per action, with construction outside timing. Raw hz is 1,000-action callback batches per second. No workload-frequency assumptions or aggregate score are used.

Preflight retained the original value/sharing assertions and added value checks for every timed replacement fixture. Returning next and retaining its prototype are permitted. All three passed these checks with the explicitly recorded constructor-key difference below; preflight completed before timing. ‡ Current main classifies the null-prototype record with own constructor: 'foo' as non-plain and returns incoming next rather than sharing previous. The PR versions classify it as plain and share previous. Thus the constructor-key rows and the mixed case containing that fixture perform different work; main's bailout is not an equivalent sharing optimization. Both changed-selector next reuse and ordinary changed-search next reuse are false on main/#8389, true on #8411, which is permitted behavior and part of the measured optimization.

The tables contain function microbenchmarks and downstream array-consumption timings, not browser navigation, allocation, memory or bundle measurements. No new conclusions about those other metrics follow from this run.

Across 180 benches: relative margin of error (RME) 0.02–1.52%; sample counts 50–169,758. Each sample contains 1,000 original loop actions. 0 benches have RME >3%; 1 have p999 >5× median.

Long-tail rows:

RME and percentiles describe within-run samples; they do not establish machine-to-machine or between-run reproducibility. Small deltas should not be generalized beyond this run. With fewer than 1,000 samples, p999 is effectively an observed extreme, not a well-resolved estimate of a population tail.

All 60 scenarios, paired in the same run
Scenario main ns #8389 ns #8411 ns #8411 vs main #8411 vs #8389
isPlainObject / literal 22.1 5.9 5.9 -73.2% +0.2%
isPlainObject / null-proto record 14.0 10.7 12.8 -8.5% +19.7%
isPlainObject / null-proto record with a constructor key ‡ 20.8 16.1 12.8 -38.3% -20.3%
isPlainObject / class instance 18.2 5.9 5.9 -67.6% -0.0%
replaceEqualDeep / equal empty objects 112.3 57.5 86.3 -23.1% +50.2%
replaceEqualDeep / equal empty null-proto objects 220.7 178.1 222.0 +0.6% +24.7%
replaceEqualDeep / equal null-proto search with a constructor key ‡ 29.9 353.1 399.7 +1236.0% +13.2%
replaceEqualDeep / equal flat search 214.9 112.3 147.6 -31.3% +31.5%
replaceEqualDeep / flat search with one changed leaf 208.5 157.3 128.8 -38.2% -18.1%
replaceEqualDeep / equal parsed null-proto search 593.0 449.8 508.7 -14.2% +13.1%
replaceEqualDeep / equal ordinary search against a null-proto previous value 427.2 278.2 337.9 -20.9% +21.5%
replaceEqualDeep / parsed null-proto search with one changed leaf 594.1 521.3 459.6 -22.6% -11.8%
replaceEqualDeep / ordinary search update in null-proto mode 430.2 346.5 285.7 -33.6% -17.6%
replaceEqualDeep / equal ordinary search in null-proto mode 254.9 112.4 148.7 -41.6% +32.3%
replaceEqualDeep / successive ordinary search update in null-proto mode 248.7 190.7 128.4 -48.4% -32.7%
replaceEqualDeep / changed selector followed by the same incoming reference 437.0 278.6 144.5 -66.9% -48.1%
replaceEqualDeep / equal record with decimal values 222.2 120.7 159.2 -28.3% +31.9%
replaceEqualDeep / changed record with decimal values 220.8 159.1 139.3 -36.9% -12.5%
replaceEqualDeep / equal nested search 498.0 280.8 354.6 -28.8% +26.3%
replaceEqualDeep / nested search sharing unchanged subtrees 498.8 346.1 365.8 -26.7% +5.7%
replaceEqualDeep / nested search sharing unchanged subtrees in null-proto mode 532.3 360.2 358.1 -32.7% -0.6%
replaceEqualDeep / equal array of objects 2,577.4 1,246.8 1,736.3 -32.6% +39.3%
replaceEqualDeep / array of objects with one changed item 2,624.5 1,507.2 1,805.3 -31.2% +19.8%
replaceEqualDeep / wide flat object with one changed leaf 464.3 416.7 282.1 -39.2% -32.3%
replaceEqualDeep / wide flat object with one changed leaf (null-proto) 597.9 546.9 283.9 -52.5% -48.1%
replaceEqualDeep / flat search with all leaves changed 201.6 152.7 133.3 -33.9% -12.7%
replaceEqualDeep / equal flat search with reordered keys 211.7 129.8 164.9 -22.1% +27.1%
replaceEqualDeep / flat search with a key removed 190.5 147.2 112.1 -41.1% -23.9%
replaceEqualDeep / array of shared objects with one changed item 540.6 517.4 364.6 -32.6% -29.5%
replaceEqualDeep / wider flat object (64 keys) with the last leaf changed 3,400.8 3,543.7 1,959.0 -42.4% -44.7%
replaceEqualDeep / flat object growing from 8 to 64 keys 2,463.0 2,429.6 1,640.5 -33.4% -32.5%
replaceEqualDeep / wider flat object (64 keys) with first leaf changed 3,406.3 2,623.6 1,856.8 -45.5% -29.2%
replaceEqualDeep / wider flat object (64 keys) with all leaves changed 2,814.8 2,796.0 2,019.2 -28.3% -27.8%
replaceEqualDeep / equal long array of shared objects 22,199.9 11,515.7 11,485.9 -48.3% -0.3%
replaceEqualDeep / long array of shared objects with the last item cloned but equal 22,409.7 11,521.3 11,553.4 -48.4% +0.3%
replaceEqualDeep / long array of shared objects with the last item changed 22,334.4 21,767.0 13,969.6 -37.5% -35.8%
replaceEqualDeep / long array of shared objects with the last item changed after a cloned but equal item 22,523.6 21,840.7 14,055.9 -37.6% -35.6%
replaceEqualDeep / equal primitive array of length 0 27.4 25.7 26.5 -3.4% +3.2%
replaceEqualDeep / equal primitive array of length 1 78.6 67.1 69.6 -11.5% +3.6%
replaceEqualDeep / equal primitive array of length 4 137.9 99.9 102.0 -26.0% +2.1%
replaceEqualDeep / equal long primitive array 20,970.3 11,499.3 11,490.1 -45.2% -0.1%
replaceEqualDeep / long primitive array with the last item changed 21,061.8 21,281.9 13,785.5 -34.5% -35.2%
replaceEqualDeep / long primitive array with first item changed 20,846.5 21,842.5 12,401.3 -40.5% -43.2%
replaceEqualDeep / long primitive array with middle item changed 20,788.7 21,580.1 13,095.5 -37.0% -39.3%
replaceEqualDeep / long primitive array with all items changed 21,264.7 21,843.5 12,374.5 -41.8% -43.3%
replaceEqualDeep / long primitive array with shortened 12,554.7 12,682.1 8,995.6 -28.3% -29.1%
replaceEqualDeep / long primitive array with appended undefined values 23,159.6 21,441.6 13,863.9 -40.1% -35.3%
replaceEqualDeep / small primitive array with the first item changed 139.2 183.7 134.2 -3.6% -27.0%
replaceEqualDeep / mixed search and array updates ‡ 473.2 357.4 364.4 -23.0% +2.0%
replaceEqualDeep / long fractional array with the last item changed 21,564.4 22,758.0 15,001.5 -30.4% -34.1%
replaceEqualDeep / long large integer array with the last item changed 22,281.5 22,634.0 14,950.3 -32.9% -33.9%
consume structurally shared arrays / sum changed primitive array by index 450.3 336.6 337.0 -25.2% +0.1%
consume structurally shared arrays / reduce changed primitive array 554.9 311.8 311.8 -43.8% -0.0%
consume structurally shared arrays / sum changed fractional array by index 637.1 637.6 637.3 +0.0% -0.0%
consume structurally shared arrays / reduce changed fractional array 625.1 646.7 643.3 +2.9% -0.5%
consume structurally shared arrays / sum changed large integer array by index 646.8 647.0 646.7 -0.0% -0.0%
consume structurally shared arrays / reduce changed large integer array 622.7 652.6 652.9 +4.8% +0.0%
deepEqual / equal empty objects 53.9 20.6 27.9 -48.2% +35.1%
deepEqual / equal flat search (partial) 61.2 29.8 29.7 -51.5% -0.3%
deepEqual / nested search with one changed leaf 119.7 57.4 61.3 -48.8% +6.8%

Focused repeat: fractional array reduction

The first run's #8389 fractional-array reduce had p999 6,099.3 ns versus median 636.4 ns, so this one scenario was repeated with all three implementations together in one process, reversing registration order to #8411, #8389, main. Warmup increased to 500 ms, measurement to at least 3,000 ms, and minimum samples to 100. No source, fixtures or timed callback bodies changed. The full table above retains every original-run mean; these repeat results are separate.

Version Mean ns/traversal Median ns/traversal p99 ns/traversal p999 ns/traversal RME Samples
main 618.8 617.4 691.6 775.3 0.10% 4,849
#8389 626.8 619.7 735.8 977.9 0.18% 4,787
#8411 614.4 599.4 716.2 769.2 0.13% 4,883

In the repeat, #8411 time was -0.7% versus main and -2.0% versus #8389. The original small regression versus main (+2.9%) did not persist; this consumer is inconclusive across runs. The outlier cause was not profiled.

Allocation and application bundle size

These are the previously completed measurements of the exact current source against #8389, separate from the three-way CPU run above. Allocation and bundle size for current main were not measured in this comparison.

Measured complete utils.ts SHA-256: aaced5f47a2bcb3107244563407fcbfb83bda205c7c69226b18487f43000fa55. Fixed #8389 baseline: 068750d2fba96dd49841e93dc8b3e3961db52181.

Changed ordinary search in null-prototype mode allocates 1,201 → 761 V8 heap bytes with a null-prototype previous value, or 793 → 353 bytes with an ordinary previous value. Additional retained output falls from about 280 → ≈0 bytes with both inputs already retained. This is not total application memory or RSS; allocation includes output, and retention is measured after full GC in three fresh processes. Equal records and copied-object arrays still allocate more temporary heap than #8389.

All 18 application scenarios were rebuilt against #8389. Total gzip increases by 48–85 bytes:

Application #8389 gzip #8411 gzip Delta
React Router minimal 85,943 B 86,017 B +74 B
Solid Router minimal 34,227 B 34,286 B +59 B
Vue Router minimal 50,739 B 50,812 B +73 B

Initial gzip changes by +48–84 B, raw JS by +62–88 B, and Brotli by −84 to +146 B. JavaScript-file counts are unchanged. The extra bytes accompany the correctness fixes and changed-object/array allocation savings.

Validation

The CPU comparison above is new. Previously completed on the current production source:

  • Unit, type, and lint targets passed for router-core, React Router, Solid Router, and Vue Router: 6,265 regular unit passes, with existing expected failures/skips retained.
  • 20 Chromium E2E tests passed: React search/structural sharing, Solid search, and Vue remount behavior.
  • An independent eager recursive oracle passed 100,000 generated pairs and 5,008,145 recursive node/sharing checks.
  • Frozen inputs, key membership/order, inherited constructors, signed zero, sparse-array shape, null-prototype copies/reuse, and the depth guard are covered.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Performance

    • Improved structural sharing to reduce unnecessary allocations and reuse unchanged data more effectively.
  • Bug Fixes

    • Improved handling of signed zero, sparse arrays, symbol properties, special values, and built-in objects.
    • Preserved object prototypes more accurately, including null-prototype records.
    • Added safeguards for deeply nested or cyclic data structures.
  • Testing

    • Expanded coverage for identity reuse, nested updates, arrays, symbols, prototypes, and special numeric values.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 8d0e04e1-9f43-4258-a97f-a6b7d35bd3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 28220be and 935bfb3.

📒 Files selected for processing (4)
  • .changeset/poor-tires-tan.md
  • packages/router-core/src/utils.ts
  • packages/router-core/tests/replace-equal-deep.bench.ts
  • packages/router-core/tests/utils.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Structural sharing

Layer / File(s) Summary
Scan and copy algorithm
packages/router-core/src/utils.ts
replaceEqualDeep now scans values before copying, reuses resolved children, validates array keys, preserves null-prototype copies, and updates recursion handling. isPlainObject now uses the actual prototype constructor.
Behavior and edge-case validation
packages/router-core/tests/utils.test.ts
Tests cover child reuse, sparse arrays, special values, cyclic inputs, null-prototype copies, built-ins, and symbol properties.
Benchmark fixtures and release metadata
packages/router-core/tests/replace-equal-deep.bench.ts, .changeset/poor-tires-tan.md
Benchmarks cover expanded object and array cases, null-prototype values, cyclic inputs, and structurally shared array consumption. A patch changeset records the release.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Suggested reviewers: schiller-manuel

Merge Risk: ⚪ Minimal · up to 935bf

No concrete current-head defect remains; the PR is ready for normal merge checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: reducing allocations in router-core's replaceEqualDeep implementation.
Description check ✅ Passed The description is detailed and covers the changes, motivation, benchmarks, validation, checklist, and release impact. One checklist item about understanding AI-assisted code remains unchecked, but th…
Linked Issues check ✅ Passed The PR meets the coding requirements in #8389. replaceEqualDeep uses separate scan and copy behavior, specialized array and object paths, shared key buffers, and structural sharing for equal childre…
Out of Scope Changes check ✅ Passed The changed files stay within #8389. The source changes optimize and correct replaceEqualDeep and its related isPlainObject classification. The unit tests verify the affected structural-sharing an…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/replace-equal-deep-child-buffers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T15:19:36.018322Z bb23d4d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@nx-cloud

nx-cloud Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 935bfb3

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 10m 54s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-15 21:18:12 UTC

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

3 package(s) bumped directly, 20 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/router-core 1.171.30 → 1.171.31 Changeset
@tanstack/solid-start 1.168.51 → 1.168.52 Changeset
@tanstack/start-plugin-core 1.171.43 → 1.171.44 Changeset
@tanstack/react-router 1.170.36 → 1.170.37 Dependent
@tanstack/react-start 1.168.53 → 1.168.54 Dependent
@tanstack/react-start-client 1.168.34 → 1.168.35 Dependent
@tanstack/react-start-rsc 0.1.52 → 0.1.53 Dependent
@tanstack/react-start-server 1.167.41 → 1.167.42 Dependent
@tanstack/router-cli 1.167.36 → 1.167.37 Dependent
@tanstack/router-generator 1.167.36 → 1.167.37 Dependent
@tanstack/router-plugin 1.168.38 → 1.168.39 Dependent
@tanstack/router-vite-plugin 1.167.38 → 1.167.39 Dependent
@tanstack/solid-router 1.170.34 → 1.170.35 Dependent
@tanstack/solid-start-client 1.168.33 → 1.168.34 Dependent
@tanstack/solid-start-server 1.167.40 → 1.167.41 Dependent
@tanstack/start-client-core 1.170.30 → 1.170.31 Dependent
@tanstack/start-server-core 1.169.35 → 1.169.36 Dependent
@tanstack/start-static-server-functions 1.167.35 → 1.167.36 Dependent
@tanstack/start-storage-context 1.167.32 → 1.167.33 Dependent
@tanstack/vue-router 1.170.33 → 1.170.34 Dependent
@tanstack/vue-start 1.168.50 → 1.168.51 Dependent
@tanstack/vue-start-client 1.167.36 → 1.167.37 Dependent
@tanstack/vue-start-server 1.167.40 → 1.167.41 Dependent

@Sheraff
Sheraff marked this pull request as draft September 13, 2026 15:15
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: ba9be51de772
  • Measured at: 2026-09-14T09:07:00.329Z
  • Baseline source: history:5facdbfe3dec
  • Dashboard: bundle-size history

The following scenarios have bundle-size changes compared with the baseline:

Scenario Current (gzip) Initial (gzip) Raw Brotli Trend
react-router.minimal 84.0 KiB
+7 B
83.9 KiB
+4 B
261.7 KiB
-21 B
73.3 KiB
+46 B
██▁▁▁▁▁▁▃▃▃▃
react-router.full 87.6 KiB
+52 B
87.4 KiB
+50 B
273.4 KiB
-11 B
76.3 KiB
+13 B
██▂▂▂▂▂▁▃▃▃▇
solid-router.minimal 33.5 KiB
+25 B
33.3 KiB
+25 B
96.0 KiB
-7 B
30.3 KiB
+27 B
▇▇▇▇▇▇▇▅▁▁▁█
solid-router.full 38.3 KiB
+52 B
38.2 KiB
+47 B
110.6 KiB
-7 B
34.6 KiB
+59 B
▂▂▂▂▂▂▂▁▁▁▁█
vue-router.minimal 49.5 KiB
+4 B
49.4 KiB
+3 B
137.1 KiB
-5 B
44.8 KiB
+22 B
██▂▂▂▂▂▂▁▁▁▁
vue-router.full 55.1 KiB
+7 B
55.0 KiB
+8 B
155.3 KiB
-5 B
49.8 KiB
+39 B
██▂▂▂▂▂▂▁▁▁▁
react-start.minimal 97.0 KiB
+37 B
96.9 KiB
+39 B
304.0 KiB
-6 B
84.1 KiB
+36 B
██▁▁▁▁▁▁▅▅▅▇
react-start.query-integration 104.3 KiB
+20 B
104.2 KiB
+20 B
330.5 KiB
+8 B
90.5 KiB
+21 B
██▁▁▁▁▁▁▅▅▅▆
react-start.deferred-hydration 97.7 KiB
+36 B
96.9 KiB
+39 B
305.3 KiB
-6 B
84.8 KiB
-9 B
██▁▁▁▁▁▁▅▅▅▇
react-start.full 100.1 KiB
+28 B
100.0 KiB
+27 B
313.7 KiB
-13 B
86.9 KiB
+69 B
██▁▁▁▁▁▁▄▄▄▅
react-start.rsbuild.minimal 100.2 KiB
+17 B
100.1 KiB
+17 B
314.4 KiB
-61 B
86.6 KiB
+21 B
██▁▁▁▁▁▁▇▇▆▇
react-start.rsbuild.minimal-iife 100.6 KiB
+15 B
100.5 KiB
+15 B
315.3 KiB
-61 B
87.0 KiB
+97 B
██▁▁▁▁▁▁▇▇▇█
react-start.rsbuild.full 103.5 KiB
+20 B
103.3 KiB
+20 B
324.5 KiB
-61 B
89.3 KiB
+13 B
██▁▁▁▁▁▁▆▆▅▆
solid-start.minimal 46.4 KiB
+37 B
46.3 KiB
+33 B
137.1 KiB
-5 B
41.3 KiB
-41 B
▁▁▁▁▁▁▁▁▅▅▅█
solid-start.deferred-hydration 49.5 KiB
+44 B
46.4 KiB
+43 B
144.5 KiB
-7 B
44.1 KiB
+90 B
▁▁▁▁▁▁▁▁▄▄▄█
solid-start.full 51.5 KiB
+25 B
51.3 KiB
+26 B
152.5 KiB
-10 B
45.7 KiB
+103 B
▁▁▁▁▁▁▁▁▆▆▆█
vue-start.minimal 65.7 KiB
+19 B
65.5 KiB
+17 B
188.0 KiB
-5 B
58.5 KiB
-73 B
██▃▃▃▃▃▃▁▁▁▁
vue-start.full 69.5 KiB
+22 B
69.4 KiB
+20 B
200.3 KiB
-5 B
61.9 KiB
-32 B
██▂▂▂▂▂▂▁▁▁▁

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@8411

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@8411

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@8411

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@8411

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@8411

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@8411

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@8411

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@8411

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@8411

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@8411

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@8411

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@8411

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@8411

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@8411

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@8411

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@8411

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@8411

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@8411

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@8411

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@8411

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@8411

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@8411

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@8411

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@8411

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@8411

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@8411

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@8411

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@8411

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@8411

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@8411

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@8411

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@8411

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@8411

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@8411

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@8411

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@8411

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@8411

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@8411

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@8411

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@8411

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@8411

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@8411

commit: 935bfb3

@codspeed

codspeed Bot commented Sep 13, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 50.05%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 179 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory mem client interrupted-navigations (solid) 252.3 KB 505.2 KB -50.05%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing codex/replace-equal-deep-child-buffers (935bfb3) with schiller-manuel-perf-replace-equal-deep-split-phase (28220be)

Open in CodSpeed

@Sheraff Sheraff changed the title perf(router-core): reuse key buffers in replaceEqualDeep perf(router-core): reduce replaceEqualDeep allocations Sep 13, 2026
@schiller-manuel
schiller-manuel added this pull request to stack #8365 September 14, 2026 09:03
@schiller-manuel
schiller-manuel force-pushed the codex/replace-equal-deep-child-buffers branch from ce00e84 to 935bfb3 Compare September 14, 2026 09:04
@schiller-manuel
schiller-manuel marked this pull request as ready for review September 16, 2026 20:06
@schiller-manuel
schiller-manuel merged commit ce10dcd into main Sep 16, 2026
27 of 29 checks passed
@schiller-manuel
schiller-manuel deleted the codex/replace-equal-deep-child-buffers branch September 16, 2026 21:12
@github-actions github-actions Bot mentioned this pull request Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants