Skip to content

test(router): repeat the requests of each match bench so a compile can't land in CodSpeed's measured call - #345

Merged
dinwwwh merged 8 commits into
mainfrom
claude/project-thread-p0gh73
Nov 2, 2026
Merged

dinwwwh merged 8 commits into
mainfrom
claude/project-thread-p0gh73

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Nov 2, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

What

The two match benches (250 RPC-style and 703 REST-style procedures) now run their request list 16 times per call, for both this router and rou3. The add bench and the prefix bench are unchanged. This is a bench-only change with no source changes.

Why

In simulation mode, CodSpeed runs each bench function a few times as warmup and then measures one call. The RPC match bench makes only 4 requests per call, about 30k instructions. A one-time V8 event, such as a Sparkplug batch compile, can land in that call and swing the result by up to 10x. This caused the false alarms on #233 (rou3 prefix -12.9%) and #336 (RPC match x2.1).

I replayed the bench the way the plugin runs it. The replay used optimizeFunction-style warmup calls, then gc(), then one call between InstrumentHooks.start/stopBenchmark, under callgrind with CodSpeed's analysis V8 flags (--no-opt --predictable ...). Instructions in the measured call:

warmup calls 1 pass, before #336 1 pass, main 16 passes, before #336 16 passes, main
5 53,596 75,341 261,803 321,344
7 324,590 32,281 261,803 321,344
9 28,098 41,465 261,803 321,344

With one pass, the result depends on where the compile lands: in one case 7 warmups read 10x higher than 9. With 16 passes, it is the same for every warmup count.

The stable numbers also show something the noise hid. Since #336, this request mix costs about 23% more in the tiers CodSpeed runs, because the POST /resource49/unknown miss now splits the path and walks the tree. In TurboFan that is about +450 instructions per miss on a method with only static routes, which #336 accepted. So expect CodSpeed on this PR to show every match bench about 16x slower, because each call does 16x the work. Later PRs then compare against a stable baseline.

I tried the same check on the prefix bench, and it is already stable with one pass (142,366 instructions for 5, 7 and 9 warmups). It stays as it is.

Checks

  • vitest bench --run packages/router runs, and the hooks (oxlint, oxfmt, tsc) pass.

Generated by Claude Code

…ead of dropping the filter

Adding a static route after the tree has served FILTER_AFTER requests used to
drop the filter, so the next request rebuilt it over every static path. Adding
routes between requests, as lazy routers do, then cost a pass over all statics
per add. Marking the new path's slot keeps the filter a superset of the
statics at O(1) per add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7PzysACDLzaReKDUKE
…t char

The filter slot was the length modulo 8 next to the last char, so paths 8
apart in length shared a slot whenever they ended in the same char. On the
GitHub, Stripe and Kubernetes path sets, 20-38% of param requests passed the
filter into a wasted statics lookup; with this slot 8-22% do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7PzysACDLzaReKDUKE
…n't land in CodSpeed's measured call

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7PzysACDLzaReKDUKE
@dinwwwh dinwwwh self-assigned this Nov 2, 2026
@codecov

codecov Bot commented Nov 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Repeat the match request lists 16x per call — packages/router/benches/router.bench.ts adds REPEAT = 16 and wraps the request loop of both match benches (ours and rou3) in it, so a one-time V8 compile finishes during warmup instead of landing in CodSpeed's single measured call. The add and prefix benches are untouched, and fresh() is still called per request inside the loop, so per-request cost stays representative for both routers.
  • Bench-only — no production source is touched; the branch-vs-base diff is this one file. The repeat workaround is the right lever here, since CodSpeed simulation mode selects its own warmup/measure iteration counts and vitest bench warmup options would not take effect.

Verified locally: uncheck packages/router/benches/router.bench.ts (oxlint, oxfmt, tsc) passes.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@codspeed

codspeed Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 77.32%

1 warning

⚠️ 4 benchmarks with a significant change ran on different hardware

Their base and head results were measured on machines with a different CPU, core count or memory, so the changes might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

❌ 4 regressed benchmarks
✅ 25 untouched benchmarks
⏩ 2 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ @openapi-spec/router 125.9 µs 925.4 µs -86.4%
❌ rou3 65.2 µs 267.4 µs -75.62%
❌ @openapi-spec/router 47.2 µs 167.8 µs -71.85%
❌ rou3 425 µs 1,500.8 µs -71.68%

Tip

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


Comparing claude/project-thread-p0gh73 (168c656) with main (8a5abff)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

dinwwwh commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

The CodSpeed result is expected. Each call of the 4 match benches now makes its request list 16 times, so the base and head numbers measure different amounts of work. Head is still well under 16x the base: 3.6x (RPC, ours), 7.4x (REST, ours), 4.1x and 3.5x (rou3). That gap is the point of this PR. Each single-pass base number included one-time V8 compile work that landed in the measured call. For example, the old RPC number of 47.2 µs for 4 requests is about 12 µs per request. With 16 passes, that one-time work is spread across many more requests, so it no longer dominates the result. These results become the new baseline. No router code changed.


Generated by Claude Code

@dinwwwh
dinwwwh merged commit f358e59 into main Nov 2, 2026
8 of 9 checks passed
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.

2 participants