Skip to content

perf(router): measure LP1 + LP4 lazy-include costs; record both as deferred - #546

Merged
ivogt merged 1 commit into
mainfrom
perf/router-lp1-lp4-measured
Jun 8, 2026
Merged

ivogt merged 1 commit into
mainfrom
perf/router-lp1-lp4-measured

Conversation

@ivogt

@ivogt ivogt commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

What & why

No behavior change. Completes the lazy-include performance audit. LP1 and LP4 were the last two items still labelled "deferred (needs-design)" — which implies planned optimization work. Measuring them (the same treatment LP3 just got in #545) shows neither is worth optimizing, so this records them as measured/deferred with numbers and a reproducible benchmark.

The numbers (vitest bench lazy-include-cost)

LP1 — an include with M routes runs its handler once per route (the cache is pruned per routeKey):

cost
warm all 30 routes of a 30-route include from cold (30 handler runs) ~0.6–0.8 ms
re-hit all 30 routes (cache hits) ~40–60 µs
→ steady state is ~20× cheaper; waste ~20–25 µs/route, once per route per isolate amortizes to 0

LP4 — a cold document request resolves twice (isSSR=false classify + isSSR=true render), both miss the isSSR-keyed cache:

cost
double resolve (current) ~1.9× a single resolve
→ waste ~20–25 µs per cold document request, once per route per isolate amortizes to 0

Verdict (all three now measured)

item cost when amortized
LP1 ~20 µs/route once per route per isolate → 0
LP3 ~0.75 µs/route once per route's first request → 0
LP4 ~20 µs/doc request once per route per isolate → 0

All three are cold-start, tens-of-microseconds, paid once, amortized to zero. The fixes are architecturally risky — LP1 needs an unpruned-cache + prune-on-read (a naive handler-identity key thrashes, verified); LP4 (and LP3) need isSSR-dependent state split out of the cached tree, touching shortCodes + the semantic matrix. Risk ≫ reward → deferred as documented decisions. No optimization is warranted.

Changes

  • lazy-include-cost.bench.ts — add LP1 (warm-all-from-cold vs re-hit) and LP4 (double vs single resolve) benches + a makeMultiRouteInclude helper; render() takes isSSR. Header now documents LP1/LP3/LP4.
  • lazy-include-perf.test.ts — rewrite the LP1 and LP4 it.todos as MEASURED / deferred with the numbers.
  • docs/internal/matching-stability-review.md — LP1 and LP4 sections rewritten as MEASURED, DEFERRED (not worth it); Follow-up notes all three are quantified and no optimization is warranted.

Test plan

  • typecheck clean (covers the bench's new helper + isSSR)
  • unit 3383 pass / 6 todo — bench excluded from the default run
  • lint + format clean; pre-commit (lint/format/e2e-bucketing) green
  • vitest bench lazy-include-cost runs and produces the tables above
  • no behavior change → no new e2e

Notes

Follow-up to #545 (LP3). LP1/LP3/LP4 are now all measured-and-documented; the lazy-include perf audit is complete with no code optimization needed.

…ferred

No behavior change. Completes the lazy-include perf audit: LP1 and LP4 were the
last two items still labelled "deferred (needs-design)" — implying planned work.
Measuring them (same as LP3) shows they are not worth optimizing either.

Measured (lazy-include-cost.bench.ts, `vitest bench lazy-include-cost`):
- LP1 (include with M routes runs its handler once per route, pruned-per-routeKey
  cache): warming all 30 routes of a 30-route include from cold is ~20x the cost
  of re-hitting them (cache hits) — ~20-25us/route, paid once per route per
  isolate, amortized to 0. Worst case (every route hit); most includes are small.
- LP4 (cold document request resolves twice, isSSR=false classify + isSSR=true
  render, both miss the isSSR-keyed cache): the double resolve is ~1.9x a single
  — ~20-25us waste per cold document request, once per route per isolate,
  amortized to 0.

Both are the same flavor as LP3: cold-start, tens of microseconds, amortized to
~0. The fixes are architecturally risky (LP1 = unpruned cache + prune-on-read;
LP4 = split isSSR-dependent state out of the cached tree, touching shortCodes +
the semantic matrix). Risk >> reward -> deferred as documented decisions.

Changes:
- lazy-include-cost.bench.ts: add LP1 (warm-all-from-cold vs re-hit) and LP4
  (double vs single resolve) benches + a makeMultiRouteInclude helper; render()
  takes isSSR. Header documents all three (LP1/LP3/LP4).
- lazy-include-perf.test.ts: rewrite the LP1 and LP4 it.todos as MEASURED/deferred
  with the numbers.
- docs/internal/matching-stability-review.md: LP1 and LP4 sections rewritten as
  MEASURED, DEFERRED (not worth it) with numbers; Follow-up notes all three are
  now quantified and no optimization is warranted.
@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@546

commit: d960ef6

@ivogt
ivogt merged commit 7f6a1bd into main Jun 8, 2026
47 of 48 checks passed
@ivogt
ivogt deleted the perf/router-lp1-lp4-measured branch June 8, 2026 12:18
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