Skip to content

Commit fe1391d

Browse files
committed
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 parent 583175d commit fe1391d

File tree

1 file changed

+8
-6
lines changed

1 file changed

+8
-6
lines changed

lib/std/fs.zig

+8-6
Original file line numberDiff line numberDiff line change
@@ -2304,7 +2304,7 @@ pub const Dir = struct {
23042304
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
23052305
defer atomic_file.deinit();
23062306

2307-
try copy_file(in_file.handle, atomic_file.file.handle);
2307+
try copy_file(in_file.handle, atomic_file.file.handle, size);
23082308
try atomic_file.finish();
23092309
}
23102310

@@ -2783,7 +2783,7 @@ const CopyFileRawError = error{SystemResources} || os.CopyFileRangeError || os.S
27832783
// Transfer all the data between two file descriptors in the most efficient way.
27842784
// The copy starts at offset 0, the initial offsets are preserved.
27852785
// No metadata is transferred over.
2786-
fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {
2786+
fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t, maybe_size: ?u64) CopyFileRawError!void {
27872787
if (comptime builtin.target.isDarwin()) {
27882788
const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
27892789
switch (os.errno(rc)) {
@@ -2806,8 +2806,9 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {
28062806
// a 32 bit value so that the syscall won't return EINVAL except for
28072807
// impossibly large files (> 2^64-1 - 2^32-1).
28082808
const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
2809-
// Terminate when no data was copied
2810-
if (amt == 0) break :cfr_loop;
2809+
// Terminate as soon as we have copied size bytes or no bytes
2810+
if (maybe_size != null and maybe_size.? == amt or amt == 0)
2811+
break :cfr_loop;
28112812
offset += amt;
28122813
}
28132814
return;
@@ -2819,8 +2820,9 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileRawError!void {
28192820
var offset: u64 = 0;
28202821
sendfile_loop: while (true) {
28212822
const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
2822-
// Terminate when no data was copied
2823-
if (amt == 0) break :sendfile_loop;
2823+
// Terminate as soon as we have copied size bytes or no bytes
2824+
if (maybe_size != null and maybe_size.? == amt or amt == 0)
2825+
break :sendfile_loop;
28242826
offset += amt;
28252827
}
28262828
}

0 commit comments

Comments
 (0)