authorgravatar for motiejus@jakstys.ltMotiejus Jakštys <motiejus@jakstys.lt> 2022-08-19 14:53:00+03:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-10-18 12:57:21-04:00
logfd10baf748aeb3b6d2394701d735098e64638a22
treecf68a2365532f691b47a4e8171747474db983ef8
parent5ec76cf5c84f3863618e220ce41d09cc9781af8a

os.copy_file_range: save a syscall for most operations

Currenty copy_file_range always uses at least two syscalls: 1. As many as it needs to do the initial copy (always 1 during my testing) 2. The last one is always when offset is the size of the file. The second syscall is used to detect the terminating condition. However, because we do a stat for other reasons, we know the size of the file, and we can skip the syscall. Sparse files: since copy_file_range expands holes of sparse files, I conclude that this layer was not intended to work with sparse files. In other words, this commit does not make it worse for sparse file society. Test program ------------ const std = @import("std"); pub fn main() !void { const arg1 = std.mem.span(std.os.argv[1]); const arg2 = std.mem.span(std.os.argv[2]); try std.fs.cwd().copyFile(arg1, std.fs.cwd(), arg2, .{}); } Test output (current master) ---------------------------- Observe two `copy_file_range` syscalls: one with 209 bytes, one with zero: $ zig build-exe cp.zig $ strace ./cp ./cp.zig ./cp2.zig |& grep copy_file_range copy_file_range(3, [0], 5, [0], 4294967295, 0) = 209 copy_file_range(3, [209], 5, [209], 4294967295, 0) = 0 $ Test output (this diff) ----------------------- Observe a single `copy_file_range` syscall with 209 bytes: $ /code/zig/build/zig build-exe cp.zig $ strace ./cp ./cp.zig ./cp2.zig |& grep copy_file_range copy_file_range(3, [0], 5, [0], 4294967295, 0) = 209 $

1 files changed, 10 insertions(+), 4 deletions(-)

lib/std/fs.zig+10-4
...@@ -2562,7 +2562,7 @@ pub const Dir = struct {...@@ -2562,7 +2562,7 @@ pub const Dir = struct {
2562 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });2562 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
2563 defer atomic_file.deinit();2563 defer atomic_file.deinit();
25642564
2565 try copy_file(in_file.handle, atomic_file.file.handle);2565 try copy_file(in_file.handle, atomic_file.file.handle, size);
2566 try atomic_file.finish();2566 try atomic_file.finish();
2567 }2567 }
25682568
...@@ -3041,7 +3041,7 @@ const CopyFileRawError = error{SystemResources} || os.CopyFileRangeError || os.S...@@ -3041,7 +3041,7 @@ const CopyFileRawError = error{SystemResources} || os.CopyFileRangeError || os.S
3041// Transfer all the data between two file descriptors in the most efficient way.3041// Transfer all the data between two file descriptors in the most efficient way.
3042// The copy starts at offset 0, the initial offsets are preserved.3042// The copy starts at offset 0, the initial offsets are preserved.
3043// No metadata is transferred over.3043// No metadata is transferred over.
3044fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {3044fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t, maybe_size: ?u64) CopyFileRawError!void {
3045 if (comptime builtin.target.isDarwin()) {3045 if (comptime builtin.target.isDarwin()) {
3046 const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);3046 const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
3047 switch (os.errno(rc)) {3047 switch (os.errno(rc)) {
...@@ -3064,7 +3064,10 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {...@@ -3064,7 +3064,10 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {
3064 // a 32 bit value so that the syscall won't return EINVAL except for3064 // a 32 bit value so that the syscall won't return EINVAL except for
3065 // impossibly large files (> 2^64-1 - 2^32-1).3065 // impossibly large files (> 2^64-1 - 2^32-1).
3066 const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);3066 const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
3067 // Terminate when no data was copied3067 // Terminate as soon as we have copied size bytes or no bytes
3068 if (maybe_size) |s| {
3069 if (s == amt) break :cfr_loop;
3070 }
3068 if (amt == 0) break :cfr_loop;3071 if (amt == 0) break :cfr_loop;
3069 offset += amt;3072 offset += amt;
3070 }3073 }
...@@ -3077,7 +3080,10 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {...@@ -3077,7 +3080,10 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {
3077 var offset: u64 = 0;3080 var offset: u64 = 0;
3078 sendfile_loop: while (true) {3081 sendfile_loop: while (true) {
3079 const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);3082 const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
3080 // Terminate when no data was copied3083 // Terminate as soon as we have copied size bytes or no bytes
3084 if (maybe_size) |s| {
3085 if (s == amt) break :sendfile_loop;
3086 }
3081 if (amt == 0) break :sendfile_loop;3087 if (amt == 0) break :sendfile_loop;
3082 offset += amt;3088 offset += amt;
3083 }3089 }