v0.2.0
All three protocol stacks are now written for this one job rather than taken
from a general-purpose crate, and shb leads wrk and h2load on every protocol.
Measured against nginx 1.31.4 on loopback, 10 s per run, 16 threads for every
tool. See the README for the full method and caveats.
| Protocol | Config | shb | wrk | h2load |
|---|---|---|---|---|
| HTTP/1.1 | 1000 connections | 993,170 | 856,476 | 796,238 |
| HTTP/2 (h2c) | 32 conns × 32 streams | 932,839 | — | 885,527 |
| HTTP/2 (h2c) | 100 conns × 100 streams | 1,255,321 | — | 1,205,942 |
| HTTP/3 | 32 conns × 32 streams | 2,054,015 | — | 1,466,884 |
What changed
Purpose-built protocol stacks. A load generator only needs to know where
one message ends and the next begins, plus the status code to tally. All three
stacks now do exactly that:
- HTTP/1.1 responses are scanned rather than parsed: the status line,
Content-Length,Transfer-EncodingandConnectionare read, every other
header is stepped over. Nothing is allocated per response. - HTTP/2 and HTTP/3 requests are one HPACK/QPACK block encoded once at
start-up from static-table references, so the encoder never touches a
dynamic table. The client advertisesSETTINGS_HEADER_TABLE_SIZE: 0and
QPACK_MAX_TABLE_CAPACITY: 0, which stops the peer indexing too — responses
then decode without a dynamic table, and only:statusis read.
That last point is where the HTTP/3 gain comes from: a profile of a saturated
worker used to spend 47% of its time Huffman-decoding header values nothing
looked at.
Completion batching. One io_uring_enter now covers a batch of
completions instead of one each, bounded by min_wait_usec so a batch that
cannot be filled does not stall. Needs Linux 6.12; without it each wait
returns on the first completion, as before.
--disable-keepalive, which reconnects for every request (HTTP/1.1 only).
Fixes
- MTU discovery was configured with an upper bound of 65527. That is QUIC's
limit but not the kernel's — IPv4 cannot carry more than 65507 bytes of UDP
payload — so the probe came backEMSGSIZE, which was treated as a dead
connection and failed every request in flight. It cost about 4% of all
HTTP/3 requests.
Notes
- The comparison in previous READMEs was measured against a server that
saturated before any of the clients did, which flattered every number.
It is now nginx with headroom, and the results changed materially. - MSRV is 1.91.