fasthttp icon indicating copy to clipboard operation
fasthttp copied to clipboard

client: set response body as net.Buffers

Open stokito opened this issue 3 years ago • 5 comments

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.

stokito avatar Nov 15 '21 00:11 stokito

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

erikdubbelboer avatar Nov 15 '21 18:11 erikdubbelboer

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.

stokito avatar Nov 16 '21 07:11 stokito

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 😄

erikdubbelboer avatar Nov 16 '21 20:11 erikdubbelboer

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:

  • We'd hold on to more buffer memory while writing the response and can only release it once the writev is done.
  • We don't get much benefits unless SetBodyRaw is used (or if the header is larger than WriteBufferSize).
  • Code complexity

Jille avatar Dec 03 '23 20:12 Jille

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.

stokito avatar Dec 03 '23 21:12 stokito