ReferenceBenchmarking methodology
Benchmarking methodology
Measure Bun's startup, throughput, and memory reproducibly without being misled by microbenchmarks
Last updated on
Why this page exists
Nearly every "Bun is N× faster than Node" number comes from a hello-world microbenchmark. Real gains depend on your workload shape: startup-sensitive CLIs, I/O-bound APIs, and CPU-bound transforms produce completely different verdicts. This page is a reproducible measurement process whose output can go into a decision record.
Principles
- Measure your own workload: real routes, real dependencies, real data volumes. Hello world only measures framework overhead.
- Change one variable at a time: runtime, version, code, data — move exactly one, or you can't attribute the result.
- Pin the environment: same machine, same CPU/memory limits, same environment variables; if you deploy in containers, measure with production limits.
- Distributions, not averages: report at least p50/p95/p99; mean latency hides the tail.
- Warm up before sampling: JIT and connection pools make the first few hundred requests unrepresentative.
Three different measurement targets
| Target | Tool | Watch out for |
|---|---|---|
| Startup time (CLIs, cold starts) | hyperfine 'bun bench.ts' 'node bench.ts' | Run the same file on both runtimes so the runtime is the only variable; hyperfine reports mean±sd and min/max — export --export-json and post-process for percentiles |
| HTTP throughput and latency | oha / wrk | The client must not bottleneck first; ramp connections to find the knee |
| Memory and long-run stability | /usr/bin/time -v, platform monitoring | Run long enough to observe GC and leaks, not startup peaks |
# Example: one discarded warmup run, then a concurrency ladder, 30s per step
oha -z 10s -c 50 http://localhost:3000/api/items > /dev/null # warmup run
oha -z 30s -c 50 http://localhost:3000/api/items
oha -z 30s -c 200 http://localhost:3000/api/itemsBun-specific attribution notes
- Bun uses JavaScriptCore; Node/Deno use V8. Some code shapes (regexes, specific builtins, numeric work) are fast paths on one engine and not the other. The difference is an engine trait, not necessarily "Bun fast/slow."
- Install and resolution speed (
bun install, cold-start imports) and request-time throughput are separate things — don't conflate them. --watch,--hot, and development modes all have measurable overhead; take every number in production mode.- Validate behavior under container CPU limits: runtime thread pools scale to visible cores, so different limits produce different conclusions.
Performance regression in CI
A performance gate isn't about peak scores — it prevents regressions:
- Pick one stable, representative scenario (one endpoint or one script);
- Pin runner specs and iteration counts; archive results with build artifacts;
- Set thresholds above the noise floor (e.g. fail only past 20% over baseline); when results are noisy, add samples before loosening tolerance;
- When a regression fires, attribute it one variable at a time: version, code, dependencies, environment.
Report format (agent-friendly too)
Workload: <endpoint/script + input shape>
Environment: <runtime + version, OS/arch, CPU/mem limits>
Command: <exact command>
Runs: <count, warmup>
Result: <p50/p95/p99, throughput, memory>
Conclusion: <what decision this number supports>A performance number without Workload and Environment enters no decision.
Official references: oha, hyperfine, Bun.serve tuning options.