-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sendfile syscall #60689
Comments
What about macOS? At least all tier1 targets should be supported at least. @rustbot modify labels: C-enhancement T-libs |
Should work on macOS too but i never had used macOS. Mac OS sendfile documentation I'm not sure it should be in I know i may be more convincing but I'm not sure myself ! In fact, i'm asking it. It seems to me to be the right way to add it to the std. But maybe it's because I don't know how it could be implemented from an external crate. |
This could be an internal specialization for pub fn send_file<R: AsRawFd, W: AsRawFd>(r: &mut R, w: &mut W) -> io::Result<u64> {...} Windows actually has a painfully close equivalent but it's specifically to copy from a file handle to a socket (which are separate object types in the Windows API) whereas https://docs.microsoft.com/en-us/windows/desktop/api/mswsock/nf-mswsock-transmitfile |
@arobase-che It's of course completely possible to build a safe wrapper around it and it's actually pretty trivial: extern crate libc;
use std::os::unix::io::AsRawFd;
use std::{io, ptr};
// mutability is not technically required but it fits API conventions
pub fn send_file<R: AsRawFd, W: AsRawFd>(r: &mut R, w: &mut W) -> io::Result<usize> {
// parameter ordering is reversed
// null pointer is an out-pointer for the offset after the read, if not null it doesn't update the file cursors which we actually do want
// last argument is the maximum number of bytes to copy but
// the documentation says it stops at 2^31-1 regardless of arch
match unsafe { libc::sendfile(w.as_raw_fd(), r.as_raw_fd(), ptr::null_mut(), usize::MAX) } {
-1 => Err(io::Error::last_os_error()),
copied => Ok(copied as usize),
}
} |
The libc wrapper function for reference: http://man7.org/linux/man-pages/man2/sendfile.2.html |
That would be quite a rabbit hole. There are lots of special copy syscalls on linux. splice: any -> pipe; pipe -> any |
Triage: I don't think this was ever implemented. |
specialize io::copy to use copy_file_range, splice or sendfile Fixes rust-lang#74426. Also covers rust-lang#60689 but only as an optimization instead of an official API. The specialization only covers std-owned structs so it should avoid the problems with rust-lang#71091 Currently linux-only but it should be generalizable to other unix systems that have sendfile/sosplice and similar. There is a bit of optimization potential around the syscall count. Right now it may end up doing more syscalls than the naive copy loop when doing short (<8KiB) copies between file descriptors. The test case executes the following: ``` [pid 103776] statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=17, ...}) = 0 [pid 103776] write(4, "wxyz", 4) = 4 [pid 103776] write(4, "iklmn", 5) = 5 [pid 103776] copy_file_range(3, NULL, 4, NULL, 5, 0) = 5 ``` 0-1 `stat` calls to identify the source file type. 0 if the type can be inferred from the struct from which the FD was extracted 𝖬 `write` to drain the `BufReader`/`BufWriter` wrappers. only happen when buffers are present. 𝖬 ≾ number of wrappers present. If there is a write buffer it may absorb the read buffer contents first so only result in a single write. Vectored writes would also be an option but that would require more invasive changes to `BufWriter`. 𝖭 `copy_file_range`/`splice`/`sendfile` until file size, EOF or the byte limit from `Take` is reached. This should generally be *much* more efficient than the read-write loop and also have other benefits such as DMA offload or extent sharing. ## Benchmarks ``` OLD test io::tests::bench_file_to_file_copy ... bench: 21,002 ns/iter (+/- 750) = 6240 MB/s [ext4] test io::tests::bench_file_to_file_copy ... bench: 35,704 ns/iter (+/- 1,108) = 3671 MB/s [btrfs] test io::tests::bench_file_to_socket_copy ... bench: 57,002 ns/iter (+/- 4,205) = 2299 MB/s test io::tests::bench_socket_pipe_socket_copy ... bench: 142,640 ns/iter (+/- 77,851) = 918 MB/s NEW test io::tests::bench_file_to_file_copy ... bench: 14,745 ns/iter (+/- 519) = 8889 MB/s [ext4] test io::tests::bench_file_to_file_copy ... bench: 6,128 ns/iter (+/- 227) = 21389 MB/s [btrfs] test io::tests::bench_file_to_socket_copy ... bench: 13,767 ns/iter (+/- 3,767) = 9520 MB/s test io::tests::bench_socket_pipe_socket_copy ... bench: 26,471 ns/iter (+/- 6,412) = 4951 MB/s ```
There's a partial implementation via #75272, you can sendfile to a socket by using |
The sendfile optimization for copying to a socket had had to be removed in #108283 since sendfile violates ordering guarantees implicit in the So this will have to be implemented as a separate API which makes fewer promises. |
The situation is pretty clear now. I think we can close the issue. |
Should we implement the sendfile syscall to TCPStream and/or UDPStream ?
Even if it's not totally portable (Linux and BSD, windows using transmitfile), I may be a convenient way to send a file effectively.
The text was updated successfully, but these errors were encountered: