perf(router-core): keep search middleware collection optimized - #8385
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe search middleware collection now uses an indexed counted loop over destination routes. A changeset records a patch release for ChangesSearch middleware optimization
Priority: ⬇️ Low Estimated code review effort: 1 (Trivial) | ~5 minutes Change: Refactor Suggested reviewers: Merge Risk: ⚪ Minimal · up to The indexed loop preserves the existing middleware behavior, and the PR is ready to merge after normal checks. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
View your CI Pipeline Execution ↗ for commit 65b6654
☁️ Nx Cloud last updated this comment at |
Bundle Size Benchmarks
The following scenarios have bundle-size changes compared with the baseline:
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. |
🚀 Changeset Version Preview7 package(s) bumped directly, 22 bumped as dependents. 🟩 Patch bumps
|
Merging this PR will improve performance by 18.29%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Memory | mem client interrupted-navigations (vue) |
282.9 KB | 218.3 KB | +29.6% |
| ⚡ | Memory | mem client interrupted-navigations (react) |
248.4 KB | 216 KB | +15.03% |
| ⚡ | Memory | mem client interrupted-navigations (solid) |
280.6 KB | 252.8 KB | +11.01% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing schiller-manuel-search-middleware-compile-memory (65b6654) with schiller-manuel-remove-build-location-sharing (f5c4a4d)1
Footnotes
CodSpeed's memory instrument flagged the interrupted-navigations client benchmark on the location-reuse changes (react +14.6%, vue +29.8%, solid +10.8% peak memory). Under its worker flags (`--no-opt --predictable`, single threaded) that benchmark's peak is set by a synchronous Maglev compile that happens inside the measured run. `getSearchMiddlewares` is compiled during the preparation calls and then deoptimized on the first property load off a route's options. Before, its only caller was the small `applySearchMiddleware`, which Maglev compiled next and inlined it into, so the callee never tiered up again. Since `build` calls it directly and does not inline it, it became hot again during the measured run, and its recompilation (about four 32 KB zone segments) set the peak. Iterating `destRoutes` with a counted loop instead of `for...of` keeps the function optimized once compiled: no deoptimization and no recompilation in the measured window. Middleware order and live route-option reads are unchanged. Hoisting the option reads alone did not help. Local reproduction with a malloc interposer, CodSpeed's worker flags and seven preparation calls, macOS arm64 / Node 24.8.0 (deterministic peaks): - react: 248880 (base) -> 287760 (+15.6%) -> 248944 with this change - vue: 256704 -> 292656 (+14.0%) -> 256640 - solid: 287408 -> 288240 (+0.3%) -> 287632 These are local native allocations, not a new Linux CodSpeed CI run. Bundle (react-router.minimal gzip): +8. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
6aec14d to
65b6654
Compare
CodSpeed's memory instrument flagged the interrupted-navigations client benchmark on the location-reuse changes (react +14.6%, vue +29.8%, solid +10.8% peak memory). Under its worker flags (`--no-opt --predictable`, single threaded) that benchmark's peak is set by a synchronous Maglev compile that happens inside the measured run. `getSearchMiddlewares` is compiled during the preparation calls and then deoptimized on the first property load off a route's options. Before, its only caller was the small `applySearchMiddleware`, which Maglev compiled next and inlined it into, so the callee never tiered up again. Since `build` calls it directly and does not inline it, it became hot again during the measured run, and its recompilation (about four 32 KB zone segments) set the peak. Iterating `destRoutes` with a counted loop instead of `for...of` keeps the function optimized once compiled: no deoptimization and no recompilation in the measured window. Middleware order and live route-option reads are unchanged. Hoisting the option reads alone did not help. Local reproduction with a malloc interposer, CodSpeed's worker flags and seven preparation calls, macOS arm64 / Node 24.8.0 (deterministic peaks): - react: 248880 (base) -> 287760 (+15.6%) -> 248944 with this change - vue: 256704 -> 292656 (+14.0%) -> 256640 - solid: 287408 -> 288240 (+0.3%) -> 287632 These are local native allocations, not a new Linux CodSpeed CI run. Bundle (react-router.minimal gzip): +8. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stacked on #8382.
Mechanism
CodSpeed's memory instrument flagged the
mem client interrupted-navigationspeak-memory benchmark on the location-reuse changes (#8370): react +14.6%, vue +29.8%, solid +10.8% versus basefc31a1d. Under CodSpeed's worker flags (--no-opt --predictable, single threaded) that benchmark's peak is set by a synchronous Maglev compile that happens inside the measured run.The function is
getSearchMiddlewaresinpackages/router-core/src/router.ts. In both base and head it is compiled during the preparation calls and then deoptimized (wrong map) on the first named property load off a route'soptions. In base its only caller was the smallapplySearchMiddleware, which Maglev compiled right afterwards and inlined it into, so the callee never tiered up again. Since #8370,buildcallsgetSearchMiddlewaresdirectly and does not inline it, so it becomes hot again during the measured run and recompiles there (about four 32 KB zone segments), setting the peak.Iterating
destRouteswith a counted loop instead offor...ofmakes the function compile once during preparation and never deoptimize, so there is no recompilation in the measured window. Middleware order and live route-option reads are unchanged. Hoisting the option reads alone did not help (and made the peak worse). This builds on the earlierperf(router-core): reduce middleware compilation memorycommit in this stack, which already separatedgetSearchMiddlewaresfrom the recursive middleware execution for the same class of reason.Diff
Plus a
@tanstack/router-corepatch changeset.Measurements
Peak outstanding malloc bytes in the measured run, reproduced locally with a malloc interposer under CodSpeed's exact worker flags (
@codspeed/coregetV8Flags()for analysis mode plus the repo's flags frombenchmarks/memory/runtime.ts), mirroring the plugin's seven preparation calls. These are local macOS arm64 / Node 24.8.0 native-allocation measurements, not a Linux CodSpeed CI run. Results are byte-deterministic across builds.a7427d662ed0ce(#8370 tip)Bundle
react-router.minimalmeasured withpnpm benchmark:bundle-size:runagainst a baseline taken at the stack top (f5c4a4d7ce):Verification
@tanstack/router-core:test:unit— 133 files, 3303 passed, 4 expected fail@tanstack/router-core:test:types— clean on all TypeScript versions (5.6–7.0)@tanstack/router-core:test:eslint— 0 errors, 27 warnings. One warning is new and inherent to this change:@typescript-eslint/prefer-for-ofat the counted loop. The rule is warning-level and the same file already carries an identical accepted warning for the counted loop inmatchRoutesInternal(line 1774). No disable comment was added.git diff --checkand prettier on the changed files — cleanSummary by CodeRabbit