go: net/http: http/2 throughput is very slow compared to http/1.1
What version of Go are you using (go version
)?
1.7
$ go version go version go1.17 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
Linux, Windows, etc
go env
Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/home/user/.cache/go-build" GOENV="/home/user/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/user/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/user/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.17" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/home/user/dev/http2issue/go.mod" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build38525640=/tmp/go-build -gno-record-gcc-switches"
What did you do?
test http/2 vs http1.1 transfert speed with client and server from standard lib
see a complete POC here: https://github.com/nspeed-app/http2issue
The issue is general, loopback (localhost) or over the wire.
What did you expect to see?
same speed order than http1.1
What did you see instead?
http/2 is at least x5 slower or worst
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 11
- Comments: 44 (8 by maintainers)
Commits related to this issue
- http2: add Transport.MaxReadFrameSize configuration setting For golang/go#47840. Fixes golang/go#54850. Change-Id: I44efec8d1f223b3c2be82a2e11752fbbe8bf2cbf Reviewed-on: https://go-review.googlesour... — committed to golang/net by robaho 3 years ago
- http2: add Transport.MaxReadFrameSize configuration setting For golang/go#47840. Fixes golang/go#54850. Change-Id: I44efec8d1f223b3c2be82a2e11752fbbe8bf2cbf Reviewed-on: https://go-review.googlesour... — committed to WeiminShang/net by robaho 3 years ago
You can pass in the http.Server to modify MaxReadFrameSize:
Yes. You need to provide similar options to the curl command to increase the buffer size.
I’ve been watching this issue as a maintainer of Caddy.
I’m seeing from the proposal https://github.com/golang/go/issues/54850 and the merged changes that changes are only being made to the HTTP/2 transport (i.e. client).
Apparently there already exists a
MaxReadFrameSize
option in the HTTP/2 server, but we’re using thehttp.Server
auto-configure right now; I don’t see any way to setMaxReadFrameSize
viahttp.Server
, unless I’m missing something obvious.Is the plan to adjust the automatic behaviour so that it’s tuned better by default for the server as well? If not, how are we meant to tune this knob? I’d rather not have to stop using
http.Server
’s auto-configure so that we can fix this performance issue.per minor release policy this should only be in for 1.20
any update to this ? is someone at Google even working on this or is there no point waiting ?
yeah, I’ve should explained myself better, sorry, in addition to increase the frame size that require manual configuration, it is an user decision to maximize throughput, I just wanted to add that maybe we can improve the default throughput with that change, since it is clear that the author left open that parameter to debate and it is a 2x win (at a cost of increasing memory cost of course, but this is inside a sync.Pool that may alleviate this problem a bit)
it does, just with a different name 😄
https://github.com/golang/net/blob/0fccb6fa2b5ce302a9da5afc2513d351bd175889/http2/http2.go#L256-L259
IIUIC the http2 code in golang/go is a bundle created from the x/net/http2
I don’t know if you are referring to this buffer in one of your comments, but using the profiler it shows a lot of contention on the
bufWriterPool
https://github.com/golang/go/blob/0e1d553b4d98b71799b86b0ba9bc338de29b7dfe/src/net/http/h2_bundle.go#L3465-L3468
Increasing the value there to the maxFrameSize value, and using the shared code in the description, you can go from 6Gbps to 12.6 Gbps
Copying my comment from https://groups.google.com/d/msgid/golang-nuts/89926c2f-ec73-43ad-be49-a8bc76a18345n%40googlegroups.com
Http2 is a multiplexed protocol with independent streams. The Go implementation uses a common reader thread/routine to read all of the connection content, and then demuxes the streams and passes the data via pipes to the stream readers. This multithreaded nature requires the use of locks to coordinate. By managing the window size, the connection reader should never block writing to a steam buffer - but a stream reader may stall waiting for data to arrive - get descheduled - only to be quickly rescheduled when reader places more data in the buffer - which is inefficient.
Out of the box on my machine, http1 is about 37 Gbps, and http2 is about 7 Gbps on my system.
Some things that jump out:
The chunk size is too small. Using 1MB pushed http1 from 37 Gbs to 50 Gbps, and http2 to 8 Gbps.
The default buffer in io.Copy() is too small. Use io.CopyBuffer() with a larger buffer - I changed to 4MB. This pushed http1 to 55 Gbs, and http2 to 8.2. Not a big difference but needed for later.
The http2 receiver frame size of 16k is way too small. There is overhead on every frame - the most costly is updating the window.
I made some local mods to the net library, increasing the frame size to 256k, and the http2 performance went from 8Gbps to 38Gbps.
I haven’t tracked it down yet, but I don’t think the window size update code is not working as intended - it seems to be sending window updates (which are expensive due to locks) far too frequently. I think this is the area that could use the most improvement - using some heuristics there is the possibility to detect the sender rate, and adjust the refresh rate (using high/low water marks).
The implementation might need improvements using lock-free structures, atomic counters, and busy-waits in order to achieve maximum performance.
So 38Gbps for http2 vs 55 Gbps for http1. Better but still not great. Still, with some minor changes, the net package could allow setting of a large frame size on a per stream basis - which would enable much higher throughput. The gRPC library allows this.