v0.2.2
A large HTTP/3 speed-up, plus a smaller HTTP/2 change.
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 | 1,967,413 | — | 1,395,173 |
HTTP/3: the QUIC state machine turns once per batch
Every received datagram used to run the whole drive step — poll the
connection for events, read the readable streams, open new ones, then
poll_transmit and submit the packets that came out. That costs about the
same whether one datagram arrived or eight, so doing it per datagram paid
it over and over, and it split outgoing packets across many small sends
instead of letting them collect into a GSO batch.
Datagrams now only mark their connection, and the state machines turn once
at the end of the completion batch. The gain grows as the streams per
connection drop, which is where each turn had least work to amortise it:
-c 64 -p 8 568,000 -> 966,000 +69.2%
-c 32 -p 32 1,065,000 -> 1,657,000 +57.4%
-c 16 -p 128 1,450,000 -> 1,553,000 +6.7%
Latency improves with it rather than paying for it: at 32×32 the median
goes from 1.12 ms to 0.578 ms and p99 from 1.67 ms to 1.22 ms, because the
queue a request waits in is shorter.
Against h2load this takes HTTP/3 from 26 % ahead to 48 % at 16 × 128.
HTTP/2: :authority is indexed in the peer's HPACK table
Requests were 28 bytes on the wire, 16 of them spelling out :authority
every time. Sending it once as a literal with incremental indexing and then
referring to that entry takes a request to 13 bytes. This does not measure
faster on loopback, where bytes are close to free; it is worth having for
runs over a real network, where halving the request bandwidth is not.
Also
- MSRV is 1.91.