aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/node/node_fs.zig123
-rw-r--r--src/bun.js/node/syscall.zig27
-rw-r--r--src/io/io_darwin.zig1
-rw-r--r--src/linux_c.zig79
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);
}