authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-07 14:37:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-16 13:12:47-07:00
log2c605872fc4fb5f4df99647ad7f4fbe7f65612a5
treeefbed2ca5a72f19a5bc633d3f57c2369b700801b
parent28e360fb617d8b5ebd0bd46d2d3a918590f55410

std.Build.Cache: non-async checking

Empirically this went from 29% slower than status quo on my machine to 15% faster, for `zig build` on the `zig init` project.

1 files changed, 18 insertions(+), 84 deletions(-)

lib/std/Build/Cache.zig+18-84
......@@ -839,13 +839,10 @@ pub const Manifest = struct {
839839 var off: usize = 0;
840840 var c: Check = .{};
841841
842 // This group we always want to compute the hash digests, even on a
843 // cache miss, because they will be used in the manifest digest.
844 var input_group: Io.Group = .init;
845 defer input_group.cancel(io);
842 // We always want to compute the input file hash digests, even on a cache miss, because they will be
843 // used in the manifest digest.
846844
847 // First the input files section, which must match our input files,
848 // otherwise it's invalid format.
845 // First the input files section, which must match our input files, otherwise it's invalid format.
849846 for (m.input_paths.items, m.files.keys()[0..m.input_paths.items.len]) |*input_path, input_file_off| {
850847 if (off + 1 >= contents.len) return error.InvalidFormat;
851848 const file_off: File.Offset = @fromBackingInt(@intCast(off));
......@@ -855,11 +852,14 @@ pub const Manifest = struct {
855852 if (path.len == 0) return error.InvalidFormat;
856853 if (input_file_off != file_off) return error.InvalidFormat;
857854
858 input_group.async(io, checkInputFile, .{ m, &c, file_off, path, input_path });
855 try checkInputFile(m, &c, file_off, path, input_path);
859856
860857 off += File.sizeOf(path.len);
861858 }
862859
860 if (c.status == .miss) return .miss;
861 if (m.diagnostic != .none) return error.CacheCheckFailed;
862
863863 // Guess number of files based on manifest contents len to reduce allocations.
864864 // This is not an upper bound; subsequent insertions may potentially allocate.
865865 try m.files.ensureUnusedCapacityContext(gpa, contents.len / (@sizeOf(File) + 32), .{
......@@ -875,6 +875,10 @@ pub const Manifest = struct {
875875 if (path.len == 0) return error.InvalidFormat;
876876
877877 try m.files.putContext(gpa, file_off, {}, .{ .contents = contents });
878 switch (try checkFile(m, &c, file_off, filePath(contents, file_off))) {
879 .hit => {},
880 .miss => return .miss,
881 }
878882
879883 off += File.sizeOf(path.len);
880884 }
......@@ -882,79 +886,12 @@ pub const Manifest = struct {
882886 // Final terminating zero byte to distinguish empty manifest file from
883887 // manifest with zero files.
884888 const file_valid = off + 1 == contents.len and contents[off] == 0;
885 if (!file_valid) {
886 try input_group.await(io);
887 return .miss;
888 }
889 if (!file_valid) return .miss;
890
891 for (m.files.keys()) |file_off| m.hash.hasher.update(&file_off.get(contents).digest);
889892
890893 // Don't track the trailing zero byte in contents.
891894 m.contents.items.len -= 1;
892 // Needed due to the length mutation above.
893 const refreshed_contents = m.contents.items;
894
895 // This group we would like to cancel as soon as a cache miss is discovered.
896 const PostResult = union(enum) {
897 checkFile: CheckFileError!Check.Status,
898 };
899 // In order to call async in the loop we need to ensure this buffer has capacity for as many elements as files
900 // being checked, otherwise a deadlock could occur since writing to the queue is waiting on the same task as
901 // would read from it.
902 const post_select_buffer = try gpa.alloc(PostResult, m.files.count() - m.input_paths.items.len);
903 defer gpa.free(post_select_buffer);
904
905 var post_select: Io.Select(PostResult) = .init(io, post_select_buffer);
906 defer post_select.cancelDiscard();
907
908 var post_select_remaining: usize = 0;
909 for (m.files.keys()[m.input_paths.items.len..]) |file_off| {
910 post_select.async(.checkFile, checkFile, .{ m, &c, file_off, filePath(refreshed_contents, file_off) });
911 post_select_remaining += 1;
912 // In case the async checkFile runs eagerly.
913 switch (@atomicLoad(Check.Status, &c.status, .unordered)) {
914 .miss => {
915 post_select.cancelDiscard();
916 try input_group.await(io);
917 return .miss;
918 },
919 .hit => {},
920 }
921 }
922
923 var post_await_buffer: [16]PostResult = undefined;
924 while (post_select_remaining > 0) {
925 const n = try post_select.awaitMany(&post_await_buffer, 1);
926 post_select_remaining -= n;
927
928 // Detect if input group already had a miss. In this case we still wait
929 // for those digests to be updated, but cancel the non input group.
930 switch (@atomicLoad(Check.Status, &c.status, .unordered)) {
931 .miss => {
932 post_select.cancelDiscard();
933 try input_group.await(io);
934 return .miss;
935 },
936 .hit => {},
937 }
938
939 for (post_await_buffer[0..n]) |u| switch (u) {
940 .checkFile => |result| switch (try result) {
941 .hit => continue,
942 .miss => {
943 post_select.cancelDiscard();
944 try input_group.await(io);
945 return .miss;
946 },
947 },
948 };
949 }
950
951 try input_group.await(io);
952 if (c.status == .miss) return .miss;
953 if (m.diagnostic != .none) return error.CacheCheckFailed;
954
955 for (m.files.keys()) |file_off| {
956 m.hash.hasher.update(&file_off.get(refreshed_contents).digest);
957 }
958895
959896 return .hit;
960897 }
......@@ -965,17 +902,14 @@ pub const Manifest = struct {
965902 file_off: File.Offset,
966903 file_path: [:0]const u8,
967904 input_path: *InputPath,
968 ) Io.Cancelable!void {
905 ) CheckFileError!void {
969906 if (input_path.have_handle) @panic("TODO");
970907 if (input_path.have_stat) @panic("TODO");
971908 if (input_path.contents != .not_requested) @panic("TODO");
972909 if (input_path.request_handle) @panic("TODO");
973 if (checkFile(m, c, file_off, file_path)) |status| switch (status) {
974 .hit => return,
975 .miss => @atomicStore(Check.Status, &c.status, .miss, .unordered),
976 } else |err| switch (err) {
977 error.CacheCheckFailed => assert(m.diagnostic != .none),
978 else => |e| return e,
910 switch (try checkFile(m, c, file_off, file_path)) {
911 .hit => {},
912 .miss => c.status = .miss,
979913 }
980914 }
981915