valyala / fasthttp

Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
MIT License
21.95k stars 1.76k forks source link

client: set response body as net.Buffers #1161

Open stokito opened 3 years ago

stokito commented 3 years ago

The net.Buffers internally uses writev syscall so we can write multiple chunks without concatenation in buffer. This significantly improves performance and I wondered why fasthttp doesn't use it.

erikdubbelboer commented 3 years ago

Does this optimization still apply when the net.Conn is wrapped in a bufio.Writer?

stokito commented 3 years ago

no, bufio is just a single array. It was added specifically for net.Conn. In the golang issues there is something like "net: Buffers makes multiple Write calls on Writers that don't implement buffersWriter" The initial code was added in scope of "proposal: net: TCPConn supports Writev" issue.

erikdubbelboer commented 3 years ago

That makes it very hard to use for us as we currently always wrap the net.Conn in a bufio.Writer. I think to fully make use of it we would also have to write the header into a []byte and prepend it to the net.Buffers so we can write everything at once.

If someone wants to make a pull request to make this all possible that would be great 😄

Jille commented 1 year ago

I've been looking into this and it seems challenging (==fun). It's inconvenient fasthttp's types export a public Write(w *bufio.Writer), because there is no way to extract the unwritten data (without copying them) from them for use with writev(2).

So I fear we need to clone all those methods to accept a custom buffer type instead of bufio.Writer and use those when no compression or TLS is used (because only then can we write straight to the file descriptor). I'm imagining the buffer type to hold a [][]byte, and the individual byte slices to be from a sync.Pool and always sized per Server.WriteBufferSize.

My throughputbuffer library is almost exactly what we want, except that you can't add a []byte without copying it in.

Drawbacks:

stokito commented 12 months ago

Given amount of efforts and additional complexity I'd rather closed the issue. But this can be used internally for headers + body. The headers are already stored in a slice.

The writev syscall similarly to sendfile can be used only for the raw HTTP but this can be used with kernel mod TLS. See Improving NGINX Performance with Kernel TLS and SSL_sendfile

P.S. For me it looks like the ideal solution would be if the net.Buffers merged into the bufio.Writer. They are naturally used in a similar way: make multiple writes and flush at once. Each writing can just append the slice instead of copying it. Unfortunately we can't change it because users (potentially) may change elements in a slice after writing.

A similar to the net.Buffers is a Rope String. In real life most of strings were created as result of at least one concatenation. Given that changing string to ropes might be huge deal.