Found while working on #761. Pre-existing on main: measured on dfd044f.
Observation
A single 64 MiB HTTP/2 response (h2c, prior knowledge) to golang.org/x/net's http2.Transport takes about 16 s on epoll, io_uring and adaptive, where std serves it in 0.07 s to the same client. net/http's own client (http.Transport with Protocols.SetUnencryptedHTTP2) gets the same response from the native engines in 0.3-0.6 s, so what the slow case depends on is the client's flow-control behaviour, and the server side is what differs from std. 1 MiB and 5 MiB bodies take 0.02-0.05 s on every engine, so the cost grows with the number of window updates the transfer needs.
| engine |
route |
64 MiB to x/net http2.Transport (main dfd044f) |
same, #805 at 689cee4 |
64 MiB to net/http's client (#805 at 222b97e) |
| std |
sync / async |
0.07 / 0.06 s |
0.06 / 0.06 s |
|
| epoll |
sync / async |
15.55 / 15.70 s |
16.88 / 16.22 s |
0.32 / 0.37 s |
| io_uring |
sync / async |
(closed at 4 MiB: #761) |
21.38 / 15.95 s |
0.63 / 0.53 s |
| adaptive |
sync / async |
15.86 / 15.87 s |
20.16 / 20.06 s |
0.43 / 0.51 s |
(x/net column: --- PASS durations of TestProbe761H2/<engine>/<route>/67108864, whose client has a 20 s timeout, so the 20-21 s cells ended at it; net/http column: TestLargeResponseIsDeliveredH2/<engine>/<route>/67108864. 689cee4 and 222b97e are earlier rounds of #805's branch; 222b97e is its head's code before the rebase onto bd17725. linux/arm64 Docker, unlimited memlock.)
Scripts: lane evidence lanes-20260927/WRITE/761/run-761.sh <ref> unlimited (probe 761/probe761_linux_test.go, test 761/large_response_linux_test.go). Logs: 761/logs/dfd044f-unlimited.log, 761/logs/689cee4-unlimited.log, 761/logs/222b97e-unlimited.log.
Not yet known
Which frames are slow was not traced. The rate, about 4 MB/s, and its independence of the route suggest the server acts on the client's WINDOW_UPDATEs late (a timer or a wait on the next recv) when the client grants small increments; a frame capture of both clients against epoll would show which. x/net 0.59's http2.Transport is deprecated in favour of net/http's, so the question is also whether other clients with small windows (browsers, curl/nghttp2) see it.
Found while working on #761. Pre-existing on main: measured on dfd044f.
Observation
A single 64 MiB HTTP/2 response (h2c, prior knowledge) to golang.org/x/net's
http2.Transporttakes about 16 s on epoll, io_uring and adaptive, where std serves it in 0.07 s to the same client. net/http's own client (http.TransportwithProtocols.SetUnencryptedHTTP2) gets the same response from the native engines in 0.3-0.6 s, so what the slow case depends on is the client's flow-control behaviour, and the server side is what differs from std. 1 MiB and 5 MiB bodies take 0.02-0.05 s on every engine, so the cost grows with the number of window updates the transfer needs.http2.Transport(main dfd044f)(x/net column:
--- PASSdurations ofTestProbe761H2/<engine>/<route>/67108864, whose client has a 20 s timeout, so the 20-21 s cells ended at it; net/http column:TestLargeResponseIsDeliveredH2/<engine>/<route>/67108864. 689cee4 and 222b97e are earlier rounds of #805's branch; 222b97e is its head's code before the rebase onto bd17725. linux/arm64 Docker, unlimited memlock.)Scripts: lane evidence
lanes-20260927/WRITE/761/run-761.sh <ref> unlimited(probe761/probe761_linux_test.go, test761/large_response_linux_test.go). Logs:761/logs/dfd044f-unlimited.log,761/logs/689cee4-unlimited.log,761/logs/222b97e-unlimited.log.Not yet known
Which frames are slow was not traced. The rate, about 4 MB/s, and its independence of the route suggest the server acts on the client's WINDOW_UPDATEs late (a timer or a wait on the next recv) when the client grants small increments; a frame capture of both clients against epoll would show which. x/net 0.59's
http2.Transportis deprecated in favour of net/http's, so the question is also whether other clients with small windows (browsers, curl/nghttp2) see it.