Skip to content

Benchmarks

Synthetic benchmarks comparing h2corn against the common Python ASGI servers across the workloads that matter: small JSON GETs, file serving, streaming, and WebSockets.

The harness lives in bench/ and drives the same Starlette application served by each of h2corn, uvicorn, hypercorn, and gunicorn with oha (HTTP cells) and k6 (WebSocket cells).

Local results

The numbers below are from a single development machine — representative of relative ordering on a quiet Linux host, not a promise about your hardware. Re-run python bench/bench.py to measure your own environment.

Methodology

Each scenario runs the same Starlette application behind every server, so any difference in throughput or latency comes from the server itself — accept loop, framing, routing, framework overhead — not from the application code.

KnobValue
Load generatoroha (HTTP), k6 (WebSocket) — separate process from the server
Duration per scenario10 s of sustained load
Concurrent VUs100 (1 000 for streaming POST)
Workers1 and 4, side-by-side per scenario
TransportsHTTP/1.1 over TCP, HTTP/1.1 over UDS, HTTP/2, WebSocket
Server sideh2corn, uvicorn, hypercorn, gunicorn (uvicorn workers)
Reverse proxyNone — k6 talks directly to each server
Servers compared on HTTP/2h2corn and hypercorn only (the other two don't speak HTTP/2)

Each server starts cold per scenario and is given a moment to settle before k6 begins; both processes run on the same host with no CPU pinning or governor tuning. The plots show a single observed run — intended to surface relative ordering at a glance, not to publish production capacity guidance.

Hardware and kernel for the displayed run are recorded as a header on each plot. To reproduce on your own box, see Reproducing.

Headline result

The most representative workload — a small JSON GET endpoint served by four workers — looks like this:

HTTP/1 GET, 4 workers

h2corn reaches ~228k RPS at p99 1.0 ms — about 10× the nearest mainstream Python server on the same deployment shape, the same Starlette app, and the standard-library asyncio stack.

HTTP/1 GET

HTTP/1 GET, 1 worker

HTTP/1 GET, 4 workers

HTTP/1 GET over Unix domain sockets

HTTP/1 GET over UDS, 1 worker

HTTP/1 GET over UDS, 4 workers

HTTP/2 GET

Only h2corn and hypercorn accept HTTP/2 directly.

HTTP/2 GET, 1 worker

HTTP/2 GET, 4 workers

Static file

HTTP/1 static file, 1 worker

HTTP/1 static file, 4 workers

HTTP/2 static file, 1 worker

HTTP/2 static file, 4 workers

Streaming POST

HTTP/1 streaming POST, 1 worker

HTTP/1 streaming POST, 4 workers

HTTP/2 streaming POST, 1 worker

HTTP/2 streaming POST, 4 workers

WebSocket

HTTP/1 WebSocket, 1 worker

HTTP/1 WebSocket, 4 workers

Reproducing

git clone https://github.com/Zaczero/pkgs.git
cd pkgs/h2corn
uv sync
uv run python bench/bench.py

The harness records per-scenario k6 summaries and renders the SVGs you see above. Output lands in bench/results/.