Repository navigation
perf(router): benchmark lazy-include double-run; record LP3 deferral with data - #545
Merged
Merged
Conversation
…with data No behavior change. Adds the timing benchmark the matching-stability review asked for and turns the vague LP3 "needs-design" item into a measured decision. LP3 is the match-time + render-time double handler execution for a non-leaf include (a module with path() routes alongside a nested include(), so it is never precomputed). Investigation + measurement narrowed and quantified it: - The common case is already 1 run: leaf includes are precomputed (C8/LP2), so only loadManifest runs the handler. The residual double-run is a non-leaf include's DIRECT routes. - Measured (lazy-include-cost.bench.ts, `vitest bench lazy-include-cost`): the redundant match run is ~0.75us/route (~7us for 5 routes, ~39us for 50) — ~50% of manifest-build time, but manifest-build is microseconds and a real RSC first request is milliseconds, so the waste is <0.5% of a cold first request. Paid once per route per isolate; amortizes to ~0 (warm cache hit ~0.8-1.8us). - Removing it is entangled with LP4: the match-time tree is built in a throwaway "lazy" namespace without isSSR (not known at match time), so reusing it for render needs isSSR-dependent state split out (the LP4 refactor, touching shortCodes + the semantic matrix). Risk >> reward -> deferred as a documented decision, not planned work. Changes: - src/router/__tests__/lazy-include-cost.bench.ts (new): vitest bench measuring construct / match (LP3 waste) / render-cold / warm across 5 and 50-route non-leaf includes. Excluded from the default test run (.bench.ts). - lazy-include-perf.test.ts: clarify the Part B "2" sentinel simulates the pre-C8 (shortcut-disabled) path and is expected to stay 2; rewrite the LP3 it.todo with the measured numbers + deferral rationale. - docs/internal/matching-stability-review.md: LP3 section rewritten as MEASURED/DEFERRED with the numbers and the LP4 entanglement; Follow-up updated (benchmark done; LP4 is the gate for revisiting LP3).
commit: |
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
No behavior change. This adds the lazy-eval /
loadManifest/ cold-start timing benchmark thatdocs/internal/matching-stability-review.mdexplicitly asked for, and uses it to turn the vague LP3 "needs-design" item into a measured decision to defer.LP3 is the match-time + render-time double handler execution for a non-leaf include — a module with
path()routes alongside a nestedinclude(), so it is never precomputed and runs its handler once inevaluateLazyEntry(match) and again inloadManifest(render).The numbers (
vitest bench lazy-include-cost)Reading: the redundant run is ~0.75 µs/route and ~50% of the manifest-build cost — but manifest-build is itself microseconds, and a real RSC first request (render + Flight + SSR) is milliseconds, so the waste is < 0.5% of a cold first request. It's paid once per route per isolate and amortizes to ~0 (warm cache hit ~0.8–1.8 µs).
Decision: defer LP3
"lazy"namespace withoutisSSR(which isn't even known at match time), so reusing it for render requires splittingisSSR-dependent state out of the cached tree — the LP4 refactor, touching shortCodes and the semantic matrix.Changes
src/router/__tests__/lazy-include-cost.bench.ts(new) —vitest bench: construct / match (LP3 waste) / render-cold / warm across 5- and 50-route non-leaf includes..bench.ts, so excluded from the defaultvitest run.lazy-include-perf.test.ts— clarify the Part B2sentinel simulates the pre-C8 (shortcut-disabled) path and is expected to stay 2; rewrite the LP3it.todowith the measured numbers + deferral rationale.docs/internal/matching-stability-review.md— LP3 section rewritten as MEASURED / DEFERRED with the numbers and the LP4 entanglement; Follow-up updated (benchmark done; LP4 gates any LP3 revisit).Test plan
.bench.ts)vitest bench lazy-include-costproduces the table aboveNotes