Skip to content

bench: add realistic workload benchmarks#179

Open
productdevbook wants to merge 2 commits intoh3js:mainfrom
productdevbook:bench/realistic-workloads
Open

bench: add realistic workload benchmarks#179
productdevbook wants to merge 2 commits intoh3js:mainfrom
productdevbook:bench/realistic-workloads

Conversation

@productdevbook
Copy link
Copy Markdown
Member

@productdevbook productdevbook commented Mar 31, 2026

Summary

  • Add comprehensive benchmarks with real-world API route sets (e-commerce 108 routes, GitHub-like API 119 routes, scalable CRUD generator up to 500+ routes)
  • Benchmark all core operations: findRoute, findAllRoutes, addRoute, removeRoute, compileRouter, and memory usage
  • Document benchmark results in README with tables for latency, scaling, and build performance

Benchmark Highlights (Apple M3 Max, Node 24)

Scenario Compiled Interpreter Speedup
E-commerce API (108 routes) 1.46 µs 3.67 µs 2.5x
GitHub-like API (119 routes) 886 ns 4.90 µs 5.5x
Route Type Latency
Static / Miss ~95 ns
Wildcard 253 ns
Param 528 ns

New files

  • test/bench/realistic-input.ts — Route sets & request data
  • test/bench/realistic.ts — Benchmark runner (6 default + 4 --full groups)

Test plan

  • pnpm bench:realistic runs successfully and produces valid output
  • generateScaleRoutes(500) produces exactly 500 routes
  • All route lookups return expected HIT/MISS results

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added comprehensive benchmarks section documenting routing performance across implementation variants
    • Added instructions for running benchmarks across Node, Bun, and Deno environments
  • Tests

    • Added realistic workload benchmark suite with diverse API route patterns and scaling tests

Add comprehensive benchmarks with real-world API route sets (e-commerce 108 routes,
GitHub-like 119 routes), route scaling tests (50/200/500), findAllRoutes, removeRoute,
compilation time, and memory usage measurements. Document results in README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@productdevbook productdevbook requested a review from pi0 as a code owner March 31, 2026 06:18
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This pull request adds comprehensive benchmarking infrastructure to the project, including realistic API workload datasets, a scalable benchmark suite supporting Node/Bun/Deno execution, documented benchmark results in the README, and helper utilities for measuring router performance across interpreter vs compiled modes, scaling characteristics, and build timings.

Changes

Cohort / File(s) Summary
Documentation & Configuration
README.md, package.json
Added "Benchmarks" and "Running Benchmarks" sections to README documenting published performance results and commands; added three new npm scripts (bench:realistic, bench:realistic:bun, bench:realistic:deno) to run the realistic workload benchmark suite.
Benchmark Infrastructure
test/bench/realistic-input.ts, test/bench/realistic.ts
Introduced new benchmark fixture file with REST-like route and request datasets (ecommerce, GitHub-like, overlapping, and scalable patterns) and a mitata-based benchmark script comparing interpreter vs compiled routing performance, scaling behavior, build timings, and memory profiling across multiple runtime conditions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • pi0

Poem

Hoppy benchmarks race so fast, 🐰⚡
Compiled routes surpass the past,
GitHub patterns, ecommerce scale,
Memory and speed both tell their tale,
Performance truths at last we see!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'bench: add realistic workload benchmarks' is clear, specific, and directly summarizes the main change—adding new benchmark infrastructure for realistic API workloads, which aligns with all modified files (README, package.json, realistic-input.ts, realistic.ts).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
test/bench/realistic.ts (2)

256-279: Memory measurements report incremental deltas rather than isolated per-router memory.

The current approach measures heap growth between successive router builds. Since memRouter50 remains reachable when memRouter200 is built, after200.heapUsed - after50.heapUsed reports the memory added by memRouter200 alone, but memRouter50 is still in memory. This seems intentional for incremental reporting, but the output labels (50 routes, 200 routes, 500 routes) may mislead users into thinking these are the standalone memory costs of each router size.

If isolated per-router measurements are desired, consider building and measuring each router independently:

♻️ Alternative: isolated per-router memory measurement
 if (typeof globalThis.gc === "function") {
   console.log("\n--- Memory Usage ---");
-  globalThis.gc();
-  const baseline = process.memoryUsage();
-
-  const memRouter50 = buildRouter(scale50.routes);
-  globalThis.gc();
-  const after50 = process.memoryUsage();
-
-  const memRouter200 = buildRouter(scale200.routes);
-  globalThis.gc();
-  const after200 = process.memoryUsage();
-
-  const memRouter500 = buildRouter(scale500.routes);
-  globalThis.gc();
-  const after500 = process.memoryUsage();
-
-  // Prevent GC from collecting routers before measurement
-  do_not_optimize(memRouter50);
-  do_not_optimize(memRouter200);
-  do_not_optimize(memRouter500);
-
-  const fmt = (bytes: number) => `${(bytes / 1024).toFixed(1)} KB`;
-  console.log(`  50 routes:  ~${fmt(after50.heapUsed - baseline.heapUsed)}`);
-  console.log(`  200 routes: ~${fmt(after200.heapUsed - after50.heapUsed)}`);
-  console.log(`  500 routes: ~${fmt(after500.heapUsed - after200.heapUsed)}`);
-  console.log(`  Total heap: ${fmt(after500.heapUsed)}`);
+  const fmt = (bytes: number) => `${(bytes / 1024).toFixed(1)} KB`;
+
+  const measure = (routes: Route[]) => {
+    globalThis.gc!();
+    const before = process.memoryUsage().heapUsed;
+    const router = buildRouter(routes);
+    globalThis.gc!();
+    const after = process.memoryUsage().heapUsed;
+    do_not_optimize(router);
+    return after - before;
+  };
+
+  console.log(`  50 routes:  ~${fmt(measure(scale50.routes))}`);
+  console.log(`  200 routes: ~${fmt(measure(scale200.routes))}`);
+  console.log(`  500 routes: ~${fmt(measure(scale500.routes))}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/bench/realistic.ts` around lines 256 - 279, The measurement currently
reports incremental heap deltas (after50, after200, after500) while keeping
earlier routers reachable (memRouter50, memRouter200), which mislabels values as
per-router costs; either change the printed labels to indicate "incremental" or
measure routers in isolation by creating each via buildRouter (memRouter50,
memRouter200, memRouter500), calling globalThis.gc(), capturing memory
(after50/after200/after500), then immediately release the reference (set
memRouterX = null), run globalThis.gc() again before the next build to ensure
previous router is collected, or alternatively spawn isolated processes for each
measurement—use the existing symbols (buildRouter,
memRouter50/memRouter200/memRouter500, do_not_optimize,
after50/after200/after500, baseline, fmt) to implement one of these fixes and
update the console labels accordingly.

191-206: Consider clarifying that removeRoute benchmark includes addRoute setup.

The removeRoute benchmark measures the cost of adding all routes and then removing them within each iteration. The benchmark name suggests it measures only removeRoute, but the timing includes both addRoute and removeRoute operations.

If you intend to measure only removeRoute latency, consider pre-building the router in the setup phase using mitata's setup mechanism.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/bench/realistic.ts` around lines 191 - 206, The benchmark currently
builds the router and calls rou3.addRoute inside each iteration, so the measured
time includes both addRoute and removeRoute; change the createCase so route
addition happens in the setup phase and the benchmark body only calls
rou3.removeRoute (or removes from a cloned/prepared router). Specifically, for
the createCase calls in the "removeRoute" group, move the loop that calls
rou3.addRoute into the setup function (using mitata's setup mechanism or by
prebuilding and returning a router from setup), have the benchmark receive that
prepared router and only call do_not_optimize(rou3.removeRoute(router, r.method,
r.path)); keep the same route data (scale50.routes and scale200.routes) and use
the existing symbols rou3.createRouter, rou3.addRoute, rou3.removeRoute,
createCase and do_not_optimize to locate and modify the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/bench/realistic.ts`:
- Around line 256-279: The measurement currently reports incremental heap deltas
(after50, after200, after500) while keeping earlier routers reachable
(memRouter50, memRouter200), which mislabels values as per-router costs; either
change the printed labels to indicate "incremental" or measure routers in
isolation by creating each via buildRouter (memRouter50, memRouter200,
memRouter500), calling globalThis.gc(), capturing memory
(after50/after200/after500), then immediately release the reference (set
memRouterX = null), run globalThis.gc() again before the next build to ensure
previous router is collected, or alternatively spawn isolated processes for each
measurement—use the existing symbols (buildRouter,
memRouter50/memRouter200/memRouter500, do_not_optimize,
after50/after200/after500, baseline, fmt) to implement one of these fixes and
update the console labels accordingly.
- Around line 191-206: The benchmark currently builds the router and calls
rou3.addRoute inside each iteration, so the measured time includes both addRoute
and removeRoute; change the createCase so route addition happens in the setup
phase and the benchmark body only calls rou3.removeRoute (or removes from a
cloned/prepared router). Specifically, for the createCase calls in the
"removeRoute" group, move the loop that calls rou3.addRoute into the setup
function (using mitata's setup mechanism or by prebuilding and returning a
router from setup), have the benchmark receive that prepared router and only
call do_not_optimize(rou3.removeRoute(router, r.method, r.path)); keep the same
route data (scale50.routes and scale200.routes) and use the existing symbols
rou3.createRouter, rou3.addRoute, rou3.removeRoute, createCase and
do_not_optimize to locate and modify the code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a29fc83f-4647-46b6-a02b-8bc6390871cd

📥 Commits

Reviewing files that changed from the base of the PR and between f6a5b4d and 352470c.

📒 Files selected for processing (4)
  • README.md
  • package.json
  • test/bench/realistic-input.ts
  • test/bench/realistic.ts

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