golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.71k stars 17.5k forks source link

proposal: io: add `WriterTo` to `PipeReader` and `ReaderFrom` to `PipeWriter` #54877

Open Jorropo opened 2 years ago

Jorropo commented 2 years ago

There is opportunities to skip synchronization events, allocations and maybe even do some zero copies (if WriteTo and ReadFrom are called concurrently).

If ReadFrom is called this would allow Read to acquire a reference to the Reader passed in and pass the buffer slice to the reader directly (skipping a possible allocation that would have been required to copy data from Reader to PipeWriter). If WriteTo is called this would allow Write to acquire a reference to the Writer passed in and pass the buffer slice to the reader directly (skipping a possible allocation that would have required to copy data from PipeReader to Writer). If ReadFrom and WriteTo are called this would allow to call io.Copy (or equivalent reusing a buffer) to maybe do a zero copy between the Writer and Reader.

The implementation would need to account for the fact that ReadFrom can be called multiple times and WriteTo need to waits until the pipe is closed, but nothing that cannot be done.

The extra channels required to pass the Writer and Reader around might be much in term of extra allocations, I guess a sync based implementation (instead of channels) would be better (as it would just embed all synchronization primitives into the pipe struct).

ianlancetaylor commented 2 years ago

This doesn't seem to match the general model for io.Pipe, which is that reads and writes are matched when possible.

Jorropo commented 2 years ago

This doesn't seem to match the general model for io.Pipe, which is that reads and writes are matched when possible.

I don't understand what this means.

I shouldn't have assumed this was obvious, but calls to WriteTo and ReadFrom would block until completion is received from the other side, exactly how Read and Write do it except it is sharing a Writer and Reader instead of []byte.

ianlancetaylor commented 2 years ago

What I mean is that io.Pipe is designed so that every call to Write matches a call to Read, when possible. If you don't want that behavior, there isn't much reason to use io.Pipe. So I think you're right that we could make io.Copy of an io.Pipe more efficient, but I don't understand how that would help any real program.

Jorropo commented 1 year ago

I think I get it now, it's that one call to write will always match one call to read (assuming the read buffer is big enough).

That seems to be a very special detail to me. When I use it, it's because I have poorly architectured code, one function wants to write the other one want to read (imagine using json.Encoder with http.NewRequest for example).

So I just throw an io.Pipe and a goroutine at it because it require order of magnitude less time than implementing a lazily json serialiser that would serialise inside .Read calls (for example, I don't often do that with json it's just an example, usually I use bytes.Buffer, I have other GiBs big formats where streaming actually matters).

However io.Pipe is not that fast for this and I was hopping to give it a bit more humpf (by skipping a copy for example).


Adding ReadFrom and WriteTo would not affect the 1 to 1 relationship of Write and Read. Also ReadFrom would also have a 1 to 1 relationship with Read (altho it's more likely your buffer isn't big enough and you have to call multiple times).