diff options
-rw-r--r-- | src/bun.js/node/node_fs.zig | 123 | ||||
-rw-r--r-- | src/bun.js/node/syscall.zig | 27 | ||||
-rw-r--r-- | src/io/io_darwin.zig | 1 | ||||
-rw-r--r-- | src/linux_c.zig | 79 |
4 files changed, 185 insertions, 45 deletions
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 93bcfc090..3a275287f 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -60,7 +60,7 @@ const FileSystemFlags = JSC.Node.FileSystemFlags; // The tagged unions for each type should become regular unions // and the tags should be passed in as comptime arguments to the functions performing the syscalls // This would reduce stack size, at the cost of instruction cache misses -const Arguments = struct { +pub const Arguments = struct { pub const Rename = struct { old_path: PathLike, new_path: PathLike, @@ -1540,6 +1540,7 @@ const Arguments = struct { mode: Mode = 0o666, file: PathOrFileDescriptor, data: StringOrBuffer, + dirfd: FileDescriptor = @intCast(FileDescriptor, std.fs.cwd().fd), pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?WriteFile { const file = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse { @@ -2766,10 +2767,14 @@ pub const NodeFS = struct { return Maybe(Return.Fsync).todo; } + pub fn ftruncateSync(args: Arguments.FTruncate) Maybe(Return.Ftruncate) { + return Maybe(Return.Ftruncate).errnoSys(system.ftruncate(args.fd, args.len orelse 0), .ftruncate) orelse + Maybe(Return.Ftruncate).success; + } + pub fn ftruncate(_: *NodeFS, args: Arguments.FTruncate, comptime flavor: Flavor) Maybe(Return.Ftruncate) { switch (comptime flavor) { - .sync => return Maybe(Return.Ftruncate).errnoSys(system.ftruncate(args.fd, args.len orelse 0), .ftruncate) orelse - Maybe(Return.Ftruncate).success, + .sync => return ftruncateSync(args), else => {}, } @@ -3418,55 +3423,89 @@ pub const NodeFS = struct { return Maybe(Return.ReadFile).todo; } - pub fn writeFile(this: *NodeFS, args: Arguments.WriteFile, comptime flavor: Flavor) Maybe(Return.WriteFile) { + pub fn writeFileWithPathBuffer(pathbuf: *[bun.MAX_PATH_BYTES]u8, args: Arguments.WriteFile) Maybe(Return.WriteFile) { var path: [:0]const u8 = undefined; - switch (comptime flavor) { - .sync => { - const fd = switch (args.file) { - .path => brk: { - path = args.file.path.sliceZ(&this.sync_error_buf); - break :brk switch (Syscall.open( - path, - @enumToInt(args.flag) | os.O.NOCTTY, - args.mode, - )) { - .err => |err| return .{ - .err = err.withPath(path), - }, - .result => |fd_| fd_, - }; + const fd = switch (args.file) { + .path => brk: { + path = args.file.path.sliceZ(pathbuf); + break :brk switch (Syscall.openat( + args.dirfd, + path, + @enumToInt(args.flag) | os.O.NOCTTY, + args.mode, + )) { + .err => |err| return .{ + .err = err.withPath(path), }, - .fd => |_fd| _fd, + .result => |fd_| fd_, }; + }, + .fd => |_fd| _fd, + }; - defer { - if (args.file == .path) - _ = Syscall.close(fd); - } + defer { + if (args.file == .path) + _ = Syscall.close(fd); + } + + var buf = args.data.slice(); + var written: usize = 0; + + // Attempt to pre-allocate large files + if (comptime Environment.isMac or Environment.isLinux) { + preallocate: { + // Worthwhile after 6 MB at least on ext4 linux + if (buf.len >= 2_000_000) { + const offset: usize = if (Environment.isMac or args.file == .path) + // on mac, it's relatively positioned + 0 + else brk: { + // on linux, it's absolutely positioned + const pos = JSC.Node.Syscall.system.lseek( + fd, + @intCast(std.os.off_t, 0), + std.os.linux.SEEK.CUR, + ); - var buf = args.data.slice(); - var written: usize = 0; + switch (JSC.Node.Syscall.getErrno(pos)) { + .SUCCESS => break :brk @intCast(usize, pos), + else => break :preallocate, + } + }; - while (buf.len > 0) { - switch (Syscall.write(fd, buf)) { - .err => |err| return .{ - .err = err, - }, - .result => |amt| { - buf = buf[amt..]; - written += amt; - if (amt == 0) { - break; - } - }, - } + bun.C.preallocate_file( + fd, + @intCast(std.os.off_t, offset), + @intCast(std.os.off_t, buf.len), + ) catch {}; } + } + } + + while (buf.len > 0) { + switch (Syscall.write(fd, buf)) { + .err => |err| return .{ + .err = err, + }, + .result => |amt| { + buf = buf[amt..]; + written += amt; + if (amt == 0) { + break; + } + }, + } + } - _ = this.ftruncate(.{ .fd = fd, .len = @truncate(JSC.WebCore.Blob.SizeType, written) }, .sync); + _ = ftruncateSync(.{ .fd = fd, .len = @truncate(JSC.WebCore.Blob.SizeType, written) }); - return Maybe(Return.WriteFile).success; - }, + return Maybe(Return.WriteFile).success; + } + + pub fn writeFile(this: *NodeFS, args: Arguments.WriteFile, comptime flavor: Flavor) Maybe(Return.WriteFile) { + switch (comptime flavor) { + .sync => return writeFileWithPathBuffer(&this.sync_error_buf, args), else => {}, } diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig index 68a29a280..970c98c94 100644 --- a/src/bun.js/node/syscall.zig +++ b/src/bun.js/node/syscall.zig @@ -192,10 +192,26 @@ pub fn getErrno(rc: anytype) std.os.E { }; } -pub fn open(file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { +pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { + if (comptime Environment.isMac) { + // https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/open-base.c + const rc = bun.AsyncIO.darwin.@"openat$NOCANCEL"(dirfd, file_path.ptr, @intCast(c_uint, flags), @intCast(c_int, perm)); + log("openat({d}, {s}) = {d}", .{ dirfd, file_path, rc }); + + return switch (Syscall.getErrno(rc)) { + .SUCCESS => .{ .result = @intCast(bun.FileDescriptor, rc) }, + else => |err| .{ + .err = .{ + .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), + .syscall = .open, + }, + }, + }; + } + while (true) { - const rc = Syscall.system.open(file_path, flags, perm); - log("open({s}): {d}", .{ file_path, rc }); + const rc = Syscall.system.openat(@intCast(Syscall.system.fd_t, dirfd), file_path, flags, perm); + log("openat({d}, {s}) = {d}", .{ dirfd, file_path, rc }); return switch (Syscall.getErrno(rc)) { .SUCCESS => .{ .result = @intCast(bun.FileDescriptor, rc) }, .INTR => continue, @@ -213,6 +229,11 @@ pub fn open(file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) unreachable; } +pub fn open(file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { + // this is what open() does anyway. + return openat(@intCast(bun.FileDescriptor, std.fs.cwd().fd), file_path, flags, perm); +} + /// This function will prevent stdout and stderr from being closed. pub fn close(fd: std.os.fd_t) ?Syscall.Error { if (fd == std.os.STDOUT_FILENO or fd == std.os.STDERR_FILENO) { diff --git a/src/io/io_darwin.zig b/src/io/io_darwin.zig index c631b5744..902ae2ae4 100644 --- a/src/io/io_darwin.zig +++ b/src/io/io_darwin.zig @@ -271,6 +271,7 @@ pub const darwin = struct { pub extern "c" fn @"accept$NOCANCEL"(sockfd: c.fd_t, noalias addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t) c_int; pub extern "c" fn @"accept4$NOCANCEL"(sockfd: c.fd_t, noalias addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t, flags: c_uint) c_int; pub extern "c" fn @"open$NOCANCEL"(path: [*:0]const u8, oflag: c_uint, ...) c_int; + pub extern "c" fn @"openat$NOCANCEL"(fd: c.fd_t, path: [*:0]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn @"read$NOCANCEL"(fd: c.fd_t, buf: [*]u8, nbyte: usize) isize; pub extern "c" fn @"pread$NOCANCEL"(fd: c.fd_t, buf: [*]u8, nbyte: usize, offset: c.off_t) isize; pub extern "c" fn @"write$NOCANCEL"(fd: c.fd_t, buf: [*]const u8, nbyte: usize) isize; diff --git a/src/linux_c.zig b/src/linux_c.zig index 0cac5fb39..0b39d1987 100644 --- a/src/linux_c.zig +++ b/src/linux_c.zig @@ -293,6 +293,85 @@ pub const SystemErrno = enum(u8) { }; pub fn preallocate_file(fd: std.os.fd_t, offset: std.os.off_t, len: std.os.off_t) anyerror!void { + // https://gist.github.com/Jarred-Sumner/b37b93399b63cbfd86e908c59a0a37df + // ext4 NVME Linux kernel 5.17.0-1016-oem x86_64 + // + // hyperfine "./micro 1024 temp" "./micro 1024 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free" + // Benchmark 1: ./micro 1024 temp + // Time (mean ± σ): 1.8 ms ± 0.2 ms [User: 0.6 ms, System: 0.1 ms] + // Range (min … max): 1.2 ms … 2.3 ms 67 runs + // Benchmark 2: ./micro 1024 temp --preallocate + // Time (mean ± σ): 1.8 ms ± 0.1 ms [User: 0.6 ms, System: 0.1 ms] + // Range (min … max): 1.4 ms … 2.2 ms 121 runs + // Summary + // './micro 1024 temp --preallocate' ran + // 1.01 ± 0.13 times faster than './micro 1024 temp' + + // hyperfine "./micro 65432 temp" "./micro 65432 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free" + // Benchmark 1: ./micro 65432 temp + // Time (mean ± σ): 1.8 ms ± 0.2 ms [User: 0.7 ms, System: 0.1 ms] + // Range (min … max): 1.2 ms … 2.3 ms 94 runs + // Benchmark 2: ./micro 65432 temp --preallocate + // Time (mean ± σ): 2.0 ms ± 0.1 ms [User: 0.6 ms, System: 0.1 ms] + // Range (min … max): 1.7 ms … 2.3 ms 108 runs + // Summary + // './micro 65432 temp' ran + // 1.08 ± 0.12 times faster than './micro 65432 temp --preallocate' + + // hyperfine "./micro 654320 temp" "./micro 654320 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free" + // Benchmark 1: ./micro 654320 temp + // Time (mean ± σ): 2.3 ms ± 0.2 ms [User: 0.9 ms, System: 0.3 ms] + // Range (min … max): 1.9 ms … 2.9 ms 96 runs + + // Benchmark 2: ./micro 654320 temp --preallocate + // Time (mean ± σ): 2.2 ms ± 0.1 ms [User: 0.9 ms, System: 0.2 ms] + // Range (min … max): 1.9 ms … 2.7 ms 115 runs + + // Warning: Command took less than 5 ms to complete. Results might be inaccurate. + + // Summary + // './micro 654320 temp --preallocate' ran + // 1.04 ± 0.10 times faster than './micro 654320 temp' + + // hyperfine "./micro 6543200 temp" "./micro 6543200 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free" + // Benchmark 1: ./micro 6543200 temp + // Time (mean ± σ): 6.3 ms ± 0.4 ms [User: 0.4 ms, System: 4.9 ms] + // Range (min … max): 5.8 ms … 8.6 ms 84 runs + + // Benchmark 2: ./micro 6543200 temp --preallocate + // Time (mean ± σ): 5.5 ms ± 0.3 ms [User: 0.5 ms, System: 3.9 ms] + // Range (min … max): 5.1 ms … 7.1 ms 93 runs + + // Summary + // './micro 6543200 temp --preallocate' ran + // 1.14 ± 0.09 times faster than './micro 6543200 temp' + + // hyperfine "./micro 65432000 temp" "./micro 65432000 temp --preallocate" --prepare="rm -rf temp && free && sync && echo 3 > /proc/sys/vm/drop_caches && free" + // Benchmark 1: ./micro 65432000 temp + // Time (mean ± σ): 52.9 ms ± 0.4 ms [User: 3.1 ms, System: 48.7 ms] + // Range (min … max): 52.4 ms … 54.4 ms 36 runs + + // Benchmark 2: ./micro 65432000 temp --preallocate + // Time (mean ± σ): 44.6 ms ± 0.8 ms [User: 2.3 ms, System: 41.2 ms] + // Range (min … max): 44.0 ms … 47.3 ms 37 runs + + // Summary + // './micro 65432000 temp --preallocate' ran + // 1.19 ± 0.02 times faster than './micro 65432000 temp' + + // hyperfine "./micro 65432000 temp" "./micro 65432000 temp --preallocate" --prepare="rm -rf temp" + // Benchmark 1: ./micro 65432000 temp + // Time (mean ± σ): 51.7 ms ± 0.9 ms [User: 2.1 ms, System: 49.6 ms] + // Range (min … max): 50.7 ms … 54.1 ms 49 runs + + // Benchmark 2: ./micro 65432000 temp --preallocate + // Time (mean ± σ): 43.8 ms ± 2.3 ms [User: 2.2 ms, System: 41.4 ms] + // Range (min … max): 42.7 ms … 54.7 ms 56 runs + + // Summary + // './micro 65432000 temp --preallocate' ran + // 1.18 ± 0.06 times faster than './micro 65432000 temp' + // _ = std.os.linux.fallocate(fd, 0, @intCast(i64, offset), len); } |