@stapelberg just made me aware of the copy_file_range syscall. Quoting the summary:
The copy_file_range() system call performs an in-kernel copy between two file descriptors without the additional cost of transferring data from the kernel to user space and then back into the kernel. It copies up to len bytes of data from the source file descriptor fd_in to the target file descriptor fd_out, overwriting any data that exists within the requested range of the target file.
It's also already supported by the sys repo: https://godoc.org/golang.org/x/sys/unix#CopyFileRange
When calling Copy, given that the conditions below are met:
runtime.GOOS == "linux"*os.FileThen, the syscall would be used to make a faster copy. I presume that we could do something similar on other platforms in the future, if they have similar syscalls.
I think CopyN and CopyBuffer could also be included here, but they are left out initially for the sake of simplicity. For example, CopyBuffer could simply ignore the buffer argument when taking the syscall shortcut, as no user space memory is required.
There is only one tricky detail here, to my mind:
If src implements the WriterTo interface, the copy is implemented by calling src.WriteTo(dst). Otherwise, if dst implements the ReaderFrom interface, the copy is implemented by calling dst.ReadFrom(src).
To not break semantics and backwards compatibility, if either of those methods exists, the syscall shortcut can't be taken. One would rely on the method having a similar syscall shortcut, if needed. Luckily, it seems like os.File implements neither method, so we're in the clear.
Isn't splice(2) and/or sendfile(2) already called by io.Copy?
http://man7.org/linux/man-pages/man2/splice.2.html
http://man7.org/linux/man-pages/man2/sendfile.2.html
copy_file_range(2) is ~recommended~ different on a kernel >= 5.3
http://man7.org/linux/man-pages/man2/copy_file_range.2.html
splice and sendfile are called by io.Copy via the (*TCPConn).ReadFrom method. I think we could add a (*os.File).ReadFrom method that calls copy_file_range.
While this can be a good performance improvement, only on kernels > 5.3 cross-file-system copies are supported. A fallback must be implemented in case copy_file_range returns -EXDEV.
@ianlancetaylor thanks for the insight. I semeed to remember splice being used for TCP. Feel free to retitle the issue to be about os.File.ReadFrom.
@beoran the original proposal did mention this, but in the context of io.Copy. I'm not sure if there is an agreed upon way to handle fallbacks in io.ReaderFrom. I guess calling io.Copy is one option. This edge case was simpler when we were dealing with the copy funcs.
The same fallback issues arise for splice. In that case if the ReadFrom method is unable to use splice, or sendfile, we call genericReadFrom in the net package, which is simply
func genericReadFrom(w io.Writer, r io.Reader) (n int64, err error) {
// Use wrapper to hide existing r.ReadFrom from io.Copy.
return io.Copy(writerOnly{w}, r)
}
It seems like we generally agree that this is doable, so I'm going to replace NeedsDecision with NeedsFix. If anyone wants to work on this, feel free to send a CL.
I am going to work on this and send a CL soon.
Change https://golang.org/cl/229101 mentions this issue: os, internal/poll, internal/syscall/unix: use copy_file_range on Linux
Change https://golang.org/cl/238864 mentions this issue: doc/go1.15: mention consequence of os.File.ReadFrom
Most helpful comment
I am going to work on this and send a CL soon.