Open timotheecour opened 3 years ago
Not that I mind these ideas, but the stdlib doesn't claim to provide the fastest (or "best") way of copying files around. And an implementation should strive for small code size and maintainability.
So far the diff is tiny, and most lines are tests anyway. :)
but the stdlib doesn't claim to provide the fastest (or "best") way of copying files around
I think the stance should be:
And an implementation should strive for small code size and maintainability.
all else being equal, yes, but not at the expense of performance where it matters.
Small (generated asm) code size is what the compiler --lean
is for (ping on https://github.com/nim-lang/Nim/pull/14282 which introduces this generally useful flag), and in future work can be used in more places to provide leaner implementations in places where this matters most.
I'm proposing to change 2nd proposal: I suggest using sendfile
instead of splice
, as it's more specific and easier to use, and also sendfile
is implemented using splice
, see:
https://stackoverflow.com/a/4483342/2108548
https://code.woboq.org/linux/linux/fs/read_write.c.html#do_sendfile
We can find a readable example of sendfile
usage in stdlib of Julia: https://github.com/JuliaLang/julia/blob/6468dcb04ea2947f43a11f556da9a5588de512a0/base/filesystem.jl#L116
What do you think?
I suggest using sendfile instead of splice
https://stackoverflow.com/a/7464280/1426932
Unfortunately, you cannot use sendfile() here because the destination is not a socket. (The name sendfile() comes from send() + "file").
In Linux kernels before 2.6.33, out_fd must refer to a socket. Since Linux 2.6.33 it can be any file. If it is a regular file, then sendfile() changes the file offset appropriately. See: https://man7.org/linux/man-pages/man2/sendfile.2.html
PS: StackOverflow is not the absolute source of Truth :-)
And yes, I would use sendfile
, as even Debian oldstable comes with a newer version of kernel.
Yes, you are correct, I should've double checked ; just added a comment in https://stackoverflow.com/questions/7463689/most-efficient-way-to-copy-a-file-in-linux/7464280#comment117018927_7464280
@rominf you'll also need to check for interrupted writes and restart from where it left off (maxes out at 2GB IIRC) along with the usual EINTR etc.
Is it OK to rewrite this code: https://github.com/python/cpython/blob/1b57426e3a7842b4e6f9fc13ffb657c78e5443d4/Lib/shutil.py#L114?
check also boot::filesystem, it recently added sendfile
support to speedup their copy_file
API: refs https://github.com/boostorg/filesystem/commit/9182b4caa34f14a246f3bcd6cae5ad9cb270682d
(but look at latest sources instead of that commit)
note also this code:
#if defined(BOOST_FILESYSTEM_USE_SENDFILE)
// sendfile started accepting file descriptors as the target in Linux 2.6.33
if (major > 2u || (major == 2u && (minor > 6u || (minor == 6u && patch >= 33u))))
cfd = ©_file_data_sendfile;
#endif
#if defined(BOOST_FILESYSTEM_USE_COPY_FILE_RANGE)
// Although copy_file_range appeared in Linux 4.5, it did not support cross-filesystem copying until 5.3
if (major > 5u || (major == 5u && minor >= 3u))
cfd = ©_file_data_copy_file_range;
#endif
which favors using the more recent copy_file_range
for recent enough linux kernel version.
copy_file_range() is useful for copying one file to another (within the same filesystem) without actually copying anything until either file is modified (copy-on-write or COW)
sendfile() only works if the source file descriptor refers to something that can be mmap()ed (i.e. mostly normal files)
sendfile is fast and has been around for a long time and it should be used by the stdlib also for sockets and [async]httpserver. Related: #304 https://github.com/nim-lang/Nim/issues/9716#issuecomment-439053314 https://github.com/nim-lang/Nim/issues/4334#issuecomment-225906512
Also, copy_file_range is even more efficient on COW filesystems by not duplicating data on disk.
[x] proposal 1
use C
copyfile
on osx (available since Mac OS X 10.5, ie 2007)implementation: https://github.com/nim-lang/Nim/pull/16883; this gives me a 2.5X speedup
[ ] proposal 2
use C
splice
on linux refs:[ ] proposal 3
on other OS (besides osx, bsd, linux), use the existing copyFile implementation but with raw IO via
open/read/write
instead of libc buffering as done currently to avoid overhead since we can read whole buffers directly (refs https://stackoverflow.com/a/7463735/1426932)[ ] proposal 4
use
8192
instead of8000
for buffer size, or even gradually increase buffer size from 4K to 16K for large files refs:EDIT: here's additional data: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ioblksize.h?id=c0a79542fb5c2c22cf0a250db94af6f8581ca342#n23
(tested on several systems) and https://github.com/coreutils/coreutils/blob/master/src/ioblksize.h#L23-L57
[ ] proposal 5: improve
copyFileWithPermissions
remove race condition and simplify
copyFileWithPermissions
on osx/bsd by callingcopyfile
withCOPYFILE_STAT
[ ] proposal 6: copyFile with extended permissions add an enum element corresponding to extended permissions on osx/freebsd (COPYFILE_XATTR, COPYFILE_ACL). This fits well with https://github.com/nim-lang/Nim/pull/16709 which added
CopyFlag
option set. This would makecopyFileWithPermissions
obsolete, given thatCopyFlag
would cover all use cases and being closer to the OS api call: you then allow:copyFile(src, std, {cfSymlinkAsIs , cfXattr, cfPerms})
[ ] proposal 7: optimizations see https://stackoverflow.com/a/7464280/1426932 which recommends:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
posix_fallocate
multithreading
[ ] proposal 8 (also a question): investigate why shell command
cp
is still a bit faster than even ccopyfile
(see benchmark in https://github.com/nim-lang/Nim/pull/16883)[ ] proposal 9: copy on write (COW) lots of OS's (osx, linux) have a COW option, this results in (unexpectedly) very large speedups. on the shell on osx for example, it's
cp -c
and the corresponding c API is clonefile => discussed in more details in https://github.com/timotheecour/Nim/issues/590links
future work
copyFile
could also benefit from zero-copy APIs