Benchmarks
hayate publishes four benchmark boundaries:
- A pinned, end-to-end competitive HTTP benchmark against FastAPI, Django, and Hono. It covers startup, cold start, dependency closure, deployment payload, throughput, and a shared HTTP contract.
- A native Cloudflare Workers benchmark against Hono, with an SDK-only raw Python control. It removes ASGI and runs all targets on the same locked Wrangler/workerd runtime.
- An in-process ASGI dispatch benchmark against Starlette. It isolates framework overhead by removing sockets and HTTP parsing.
- A raw-ASGI transport profile inside the competitive suite. It executes the same four workloads on hayate's locked Uvicorn/asyncio/h11 environment and reports how much of that transport ceiling Hayate reaches.
Do not combine their request rates: they measure different boundaries.
The native Workers methodology and reproduction command live under
benchmarks/competitive/workers/.
The default Hayate target enters through WorkerEntrypoint.fetch;
hayate-global separately measures Cloudflare's global-handler compatibility
path. ASGI, Uvicorn, and h11 are absent from both. Three framework-free Python
controls separate framework cost, FFI cost, and the current class-entrypoint
RPC wrapper. The local process-start metric is deliberately not labeled as a
deployed Cloudflare edge cold start.
Native Cloudflare Workers benchmark
Current native Workers baseline (Hayate 0.15.1, 2026-07-27)
arm, macOS-26.5.1-arm64-arm-64bit-Mach-O, arm64, CPython 3.14.6, Node 24.18.0; Wrangler
4.114.0 / workerd 1.20260722.1 / local / compatibility-date runtime; 20 connections, 10
seconds per scenario, 3 rotating rounds. The source under test is commit accb474. All
72 throughput samples and 1,657,005 requests completed with zero errors, timeouts, or
non-2xx responses. Fresh process startup required 0 bounded retries; a second
consecutive failure would have failed the run.
| Target | Version | Local first response | gzip upload | Throughput | CPU s / 1k req | Peak tree RSS | HTTP contract |
|---|---|---|---|---|---|---|---|
| Hayate class | 0.15.1 | 4,938.4 ms | 373.6 KiB | 1,904 req/s | 0.8772 | 1,839.2 MiB | 14/14 (100.0%) |
| Hayate global compatibility | 0.15.1 | 4,133.0 ms | 373.6 KiB | 2,896 req/s | 0.5519 | 1,913.1 MiB | 14/14 (100.0%) |
| Raw Python SDK class | 1.6.3 | 4,335.1 ms | 123.7 KiB | 1,878 req/s | 0.8939 | 1,431.1 MiB | 14/14 (100.0%) |
| Raw JS objects class | 1.6.3 | 3,998.0 ms | 124.3 KiB | 1,939 req/s | 0.8716 | 1,460.0 MiB | 14/14 (100.0%) |
| Raw JS objects global | Pyodide built-in | 4,491.7 ms | 124.3 KiB | 2,872 req/s | 0.5446 | 1,495.7 MiB | 14/14 (100.0%) |
| Hono | 4.12.32 | 550.9 ms | 38.0 KiB | 2,836 req/s | 0.5116 | 1,213.5 MiB | 12/14 (85.7%) |
Hayate measured at 98.17% of the raw class-path control and 100.86% of the raw global-handler control. This isolates framework overhead from the Python Workers runtime and entrypoint boundaries on the declared workload.
Against Hono, Hayate reached 67.14% through the default class entrypoint and 102.14% through the global-handler compatibility path. The 2.14% global-path difference is treated as shared-host throughput parity, not a hard victory or regression threshold. Hono remained ahead on resource efficiency: Hayate global used 1.08x CPU per request, 1.58x peak process-tree RSS, 9.83x compressed upload, and 7.50x local startup time.
ASGI, Uvicorn, and h11 are absent from this profile. The default Hayate target uses
WorkerEntrypoint.fetch; the global target uses disable_python_no_global_handlers,
which is a compatibility path, not Cloudflare's current default. Local Wrangler startup
is not deployed edge cold start, raw controls are runtime/FFI boundaries rather than
frameworks, and the 14-case HTTP contract is the declared workload boundary rather than
a universal standards score.
Historical evidence remains immutable:
This section is generated from benchmarks/competitive/workers/current.toml and the
selected raw report. Regenerate it with:
uv run python benchmarks/competitive/workers/publish.py
Competitive HTTP benchmark
The implementations, exact dependency locks, methodology, raw-result schema,
and one-command reproduction instructions live in
benchmarks/competitive/.
The full publication profile is:
python3 benchmarks/competitive/runner.py all \
--connections 50 \
--duration 10 \
--rounds 3 \
--cold-rounds 7
Python targets run on the same Uvicorn asyncio + h11 transport. Hono runs on its official Node adapter. The load generator is the same checksum-pinned oha 1.15.0 binary for every target. Raw JSON records all samples, resolved transitive versions, Git commit, CPU, operating system, and configuration.
The 14-point HTTP contract is a common-workload compatibility rate, not a universal standards score. hayate's WPT-based URL and URLPattern results are reported on the conformance page; unsupported public APIs in other frameworks are not converted into artificial zeroes.
Framework capability breadth is tracked separately in the dated, source-backed competitive capability matrix. It has no weighted score and keeps Django's full-stack strengths and Hono's JavaScript-edge strengths visible.
The monthly and manually dispatchable Competitive benchmark workflow uploads the raw JSON and Markdown summary. Shared-runner measurements are not used as a hard regression gate because host contention is uncontrolled.
Current released baseline (Hayate 0.15.1, 2026-07-27)
Apple M2 Pro, macOS 26.5.1, arm64, CPython 3.14.6, Node 24.18.0; 50 connections, 10
seconds per scenario, 3 rotating rounds. The source under test is commit 561bcf0. All
60 throughput samples completed with zero errors, timeouts, or non-2xx responses.
| Framework | Version | App import | Cold start | Production packages | gzip payload | Throughput geo mean | HTTP contract |
|---|---|---|---|---|---|---|---|
| Hayate | 0.15.1 | 96.7 ms | 149.0 ms | 5 | 298.1 KiB | 14,906 req/s | 14/14 (100.0%) |
| FastAPI | 0.140.0 | 454.6 ms | 471.4 ms | 13 | 2,802.2 KiB | 10,086 req/s | 12/14 (85.7%) |
| Django | 6.0.7 | 373.4 ms | 392.6 ms | 6 | 5,147.2 KiB | 2,557 req/s | 12/14 (85.7%) |
| Hono | 4.12.32 | 55.3 ms | 61.3 ms | 2 | 281.5 KiB | 59,187 req/s | 12/14 (85.7%) |
On this workload, Hayate delivered 1.48x FastAPI's and 5.83x Django's throughput. FastAPI and Django took 3.16x and 2.64x as long to cold-start. Hono delivered 3.97x Hayate's throughput, and Hayate took 2.43x as long to cold-start.
Hayate's runtime-excluded compressed payload was 5.9% larger than Hono's official Node stack while using 3 more production packages. FastAPI's and Django's payloads were 9.40x and 17.27x Hayate's.
The same run measured the raw Uvicorn/asyncio/h11 workload ceiling at 16,765 req/s. Hayate reached 88.9% of that ceiling overall and 86.0% to 94.0% across the four workloads. Hono was 3.53x faster than raw Uvicorn itself, so most of the remaining Hono gap belongs to the runtime/transport boundary rather than Hayate's framework core.
The full reports contain every sample, latency percentile, resolved package version, and machine field. These numbers are a reproducible baseline, not a claim about all applications or hardware.
Historical evidence remains immutable:
This section is generated from benchmarks/competitive/current.toml and the selected
raw report. Regenerate it with:
uv run python benchmarks/competitive/publish.py
The suite also measures a raw ASGI implementation of the same four workloads inside hayate's locked Python environment. This separates framework overhead from the Uvicorn/h11 versus Node transport difference; the raw target is not ranked as a framework and is excluded from startup and payload comparisons.
In-process ASGI dispatch
This historical benchmark measures hayate against Starlette with no sockets or HTTP parsing. Both frameworks are driven directly through their ASGI callable with a no-op transport and a fresh scope per request.
Run:
uv run --group bench python benchmarks/bench.py
Results (2026-07-22, hayate 0.6.0, Tier 1 + Tier 2 accelerator)
Apple Silicon (arm64), CPython 3.14.6, hayate 0.6.0 with hayate-accel, starlette 1.3.1. N=10,000 per round, best of 3 rounds.
| Scenario | hayate req/s | Starlette req/s | Ratio |
|---|---|---|---|
| static-text | 243,782 | 192,686 | 1.27x |
| dynamic-json | 170,337 | 154,827 | 1.10x † |
| many-routes(64) | 200,097 | 52,190 | 3.83x |
| middleware(2) | 155,975 | 3,512 | 44.41x * |
† 0.6.0's segment-trie router is flat at ~0.95 µs/match regardless of route count (many-routes went 1.93x → 3.83x), but a single dynamic route is the linear scan's absolute best case (one C-regex call, ~0.5 µs) — this scenario gave back ~8%. Real apps with more than a couple of routes come out ahead; the trade-off and the rejected alternatives are recorded in DESIGN §14.4.
* Starlette's stock middleware mechanism (BaseHTTPMiddleware) has a
well-known high overhead (a task plus stream re-wrapping per request);
hand-written raw-ASGI middleware would narrow this. hayate's onion
composition adds no tasks, so the number is real but the comparison is
of each framework's standard middleware story.
Without the accelerator (pure Python, Tier 0+1), dynamic-json measures 0.99x — parity; the Rust JSON encoder buys the remaining +23%.
The measured floor (a raw ASGI function doing nothing but two send
calls) is ~1.96M req/s (0.51µs/req); the "framework tax" is what both
frameworks add on top.
Progression (static-text / dynamic-json vs Starlette)
| Stage | static | dynamic-json |
|---|---|---|
| v0.1 initial (eager Fetch objects) | 0.43x | 0.44x |
| + trusted adapter fast paths | 0.96x | 0.86x |
| + Tier 1 lazy materialization | 1.20x | 0.99x |
| + Tier 2 Rust JSON encoder | 1.20x | 1.22x |
The tiers (DESIGN.md §14)
- Tier 1 (pure Python, everywhere incl. Pyodide): header bytes kept wire-native and decoded lazily; bodyless requests carry a null body per Fetch (no stream, no signal); per-request allocations created on first use; middleware chains precomputed when unscoped (Stage 2).
- Tier 2 (
hayate-accel, Rust/PyO3, abi3 ≥3.12): compact JSON encoder behaviorally identical tojson.dumps(..., ensure_ascii=False, separators=(",", ":"))for supported types; anything else raisesTypeErrorand falls back to the stdlib. Since 0.2.0 it also accelerates multipart parsing: the boundary scan uses SIMD substring search (memchr::memmem) and copies each payload once —parse_multiparton a 10.5 MB body with two file parts drops from 5.7 ms to 0.5 ms (11x, Apple Silicon). Semantic parsing stays in the pure-Python path; the two splitters are pinned identical by parity tests. Build locally:
uv run --with maturin maturin build --release -m accel/Cargo.toml -o dist-accel
uv pip install dist-accel/*.whl
Numbers shift with hardware and Python versions; re-run locally before drawing conclusions.