authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-13 23:23:54 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-13 23:23:54 -07:00
logf0621c440300a072e2da9b9925b4049228c20ad8
tree81781c0664a133cd49bdc69dd8e03d82d4a6157b
parenta24c14cfda1ec666069a91eb875addfcab2413ed
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

remove MultiArrayList and the need to sort in getTree to use binary search in Tree.get


1 files changed, 39 insertions(+), 148 deletions(-)

git.zig+39-148
......@@ -761,7 +761,7 @@ pub const Repository = struct {
761761 for (r.pack_content.values()) |v| nfs.munmap(v);
762762 r.pack_content.deinit(r.gpa);
763763 r.commits.deinit(r.gpa);
764 for (r.trees.values()) |*v| v.children.deinit(r.gpa);
764 for (r.trees.values()) |*v| r.gpa.free(v.children);
765765 r.trees.deinit(r.gpa);
766766 r.tags.deinit(r.gpa);
767767 }
......@@ -1097,8 +1097,7 @@ pub const Repository = struct {
10971097 }
10981098 if (try r.getObject(arena, id.id)) |obj| {
10991099 if (obj.type == .tree) {
1100 const MAL = MultiArrayList(Tree.Object);
1101 var children: MAL = .empty;
1100 var children: std.ArrayList(Tree.Object) = .empty;
11021101 errdefer children.deinit(r.gpa);
11031102 try children.ensureUnusedCapacity(r.gpa, 33);
11041103 var i: usize = 0;
......@@ -1133,24 +1132,9 @@ pub const Repository = struct {
11331132 });
11341133 }
11351134
1136 const S = struct {
1137 list: *MAL,
1138
1139 pub fn lessThan(s: @This(), a: usize, b: usize) bool {
1140 const items = s.list.items.name;
1141 return std.mem.order(u8, items[a], items[b]) == .lt;
1142 }
1143 pub fn swap(s: @This(), a: usize, b: usize) void {
1144 // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016
1145 inline for (@typeInfo(Tree.Object).@"struct".fields) |field| {
1146 const its = @field(s.list.items, field.name);
1147 std.mem.swap(@FieldType(Tree.Object, field.name), &its[a], &its[b]);
1148 }
1149 }
1150 };
1151 if (children.len() > 1000) std.mem.sortContext(0, children.len(), S{ .list = &children });
1152
1153 const tree: Tree = .{ .children = children };
1135 const children_slice = try children.toOwnedSlice(r.gpa);
1136 errdefer r.gpa.free(children_slice);
1137 const tree: Tree = .{ .children = children_slice };
11541138 try r.trees.put(r.gpa, id.id, tree);
11551139 return .{ id, tree };
11561140 }
......@@ -1263,12 +1247,12 @@ pub const Repository = struct {
12631247 const base = base_idx.reify(r);
12641248 const base_tree_id = (try traverseTo(r, arena, base.tree, dir_path)).?;
12651249 const base_tree = try r.getTreeA(arena, base_tree_id.id);
1266 const total = base_tree.children.len();
1250 const total = base_tree.children.len;
12671251
12681252 var found: usize = 0;
12691253 var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty;
12701254 defer result.deinit(r.gpa);
1271 for (base_tree.children.items.name) |name| try result.put(r.gpa, name, undefined);
1255 for (base_tree.children) |obj| try result.put(r.gpa, obj.name, undefined);
12721256
12731257 var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total);
12741258 defer set.deinit(r.gpa);
......@@ -1301,7 +1285,7 @@ pub const Repository = struct {
13011285 const tree = try r.getTreeA(arena, tree_id.id);
13021286 for (result.keys(), 0..) |k, i| {
13031287 if (set.isSet(i)) continue;
1304 const new = tree.get(k);
1288 const new = tree.get(k, base_tree.children[i].mode.type);
13051289 if (new == null) {
13061290 found += 1;
13071291 result.putAssumeCapacity(k, commit_id_prev);
......@@ -1309,7 +1293,7 @@ pub const Repository = struct {
13091293 // std.log.debug("found [{d}/{d}] objects after searching {d} commits", .{ found, total, searched });
13101294 continue;
13111295 }
1312 if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children.items.id[i].erase())) {
1296 if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children[i].id.erase())) {
13131297 found += 1;
13141298 result.putAssumeCapacity(k, commit_id_prev);
13151299 set.set(i);
......@@ -1393,7 +1377,7 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di
13931377 var iter = std.mem.splitScalar(u8, dir_path, '/');
13941378 while (iter.next()) |segment| {
13951379 const tree = try r.getTreeA(arena, id.id);
1396 const o = tree.get(segment) orelse return null;
1380 const o = tree.get(segment, .directory) orelse return null;
13971381 if (o.id != .tree) return null;
13981382 id = o.id.tree;
13991383 }
......@@ -1401,31 +1385,23 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di
14011385}
14021386
14031387pub const Tree = struct {
1404 children: MultiArrayList(Object),
1388 children: []const Object,
14051389
1406 pub fn get(self: Tree, name: string) ?Object {
1407 if (self.children.len() <= 1000) {
1408 for (self.children.items.name, 0..) |a_name, i| {
1409 if (std.mem.eql(u8, a_name, name)) {
1410 return self.children.get(i);
1411 }
1412 }
1413 return null;
1414 }
1415 const i = std.sort.binarySearch([]const u8, self.children.items.name, name, extras.compareFnSlice(u8)) orelse return null;
1416 return self.children.get(i);
1390 pub fn get(self: Tree, name: string, hint: Object.Type) ?Object {
1391 const i = std.sort.binarySearch(Object, self.children, .{ name, hint }, Object.search) orelse return null;
1392 return self.children[i];
14171393 }
14181394
1419 pub fn getBlob(self: Tree, name: string) ?Object {
1420 const o = self.get(name) orelse return null;
1395 pub fn getBlob(self: Tree, name: string, hint: Object.Type) ?Object {
1396 const o = self.get(name, hint) orelse return null;
14211397 if (o.id != .blob) return null;
14221398 return o;
14231399 }
14241400
14251401 pub fn find(self: Tree, name: string) ?Object {
1426 for (self.children.items.name, 0..) |item, i| {
1427 if (std.ascii.eqlIgnoreCase(item, name)) {
1428 return self.children.get(i);
1402 for (self.children, 0..) |item, i| {
1403 if (std.ascii.eqlIgnoreCase(item.name, name)) {
1404 return self.children[i];
14291405 }
14301406 }
14311407 return null;
......@@ -1442,6 +1418,24 @@ pub const Tree = struct {
14421418 id: AnyId,
14431419 name: [:0]const u8,
14441420
1421 fn search(a: struct { []const u8, Type }, b: Object) std.math.Order {
1422 if (a[0].ptr != b.name.ptr) {
1423 const n = @min(a[0].len, b.name.len);
1424 for (a[0][0..n], b.name[0..n]) |lhs_elem, rhs_elem| {
1425 switch (std.math.order(lhs_elem, rhs_elem)) {
1426 .eq => continue,
1427 .lt => return .lt,
1428 .gt => return .gt,
1429 }
1430 }
1431 }
1432 return switch (std.math.order(a[0].len, b.name.len)) {
1433 .lt => if (a[1] == .directory) std.math.order('/', b.name[a[0].len]) else .lt,
1434 .gt => if (b.mode.type == .directory) std.math.order(a[0][b.name.len], '/') else .gt,
1435 .eq => .eq,
1436 };
1437 }
1438
14451439 pub const Mode = struct {
14461440 type: Type,
14471441 perm_user: Perm,
......@@ -1581,8 +1575,8 @@ pub const Tree = struct {
15811575 var top = &self.stack.items[self.stack.items.len - 1];
15821576 var containing = top;
15831577 var dirname_len = top.dirname_len;
1584 if (top.idx < top.tree.children.len()) {
1585 const base = top.tree.children.get(top.idx);
1578 if (top.idx < top.tree.children.len) {
1579 const base = top.tree.children[top.idx];
15861580 top.idx += 1;
15871581 self.name_buffer.shrinkRetainingCapacity(dirname_len);
15881582 if (self.name_buffer.items.len != 0) {
......@@ -1652,106 +1646,3 @@ pub const Ref = struct {
16521646 oid: Id,
16531647 label: string,
16541648};
1655
1656/// Variant on std.MultiArrayList but using an allocation per-field rather than a single for the entire structure.
1657fn MultiArrayList(T: type) type {
1658 return struct {
1659 items: Items,
1660 capacity: usize,
1661
1662 comptime Elem: type = T,
1663
1664 pub const empty: Self = .{
1665 .items = .{},
1666 .capacity = 0,
1667 };
1668
1669 pub const Self = @This();
1670
1671 const info = @typeInfo(T).@"struct";
1672
1673 pub const Items = blk: {
1674 var names: [info.fields.len][]const u8 = undefined;
1675 var types: [info.fields.len]type = undefined;
1676 var attrs: [info.fields.len]std.builtin.Type.StructField.Attributes = undefined;
1677 for (info.fields, 0..) |f, i| {
1678 const empty_slice: []f.type = &[_]f.type{};
1679 names[i] = f.name;
1680 types[i] = []f.type;
1681 attrs[i] = .{ .default_value_ptr = @ptrCast(&empty_slice) };
1682 }
1683 break :blk @Struct(.auto, null, &names, &types, &attrs);
1684 };
1685
1686 pub fn deinit(self: *const Self, gpa: std.mem.Allocator) void {
1687 inline for (info.fields) |f| {
1688 gpa.free(@field(self.items, f.name).ptr[0..self.capacity]);
1689 }
1690 }
1691
1692 pub fn len(self: *const Self) usize {
1693 return @field(self.items, info.fields[0].name).len;
1694 }
1695
1696 pub fn get(self: *const Self, index: usize) T {
1697 var result: T = undefined;
1698 inline for (info.fields) |f| {
1699 @field(result, f.name) = @field(self.items, f.name)[index];
1700 }
1701 return result;
1702 }
1703
1704 pub fn ensureUnusedCapacity(self: *Self, gpa: std.mem.Allocator, additional_count: usize) !void {
1705 return self.ensureTotalCapacity(gpa, self.len() + additional_count);
1706 }
1707
1708 pub fn ensureTotalCapacity(self: *Self, gpa: std.mem.Allocator, new_capacity: usize) !void {
1709 if (self.capacity >= new_capacity) return;
1710 const actual_new_capacity = growCapacity(self.capacity, new_capacity);
1711 const previous_len = self.len();
1712 var new_items: Items = .{};
1713 inline for (info.fields, 0..) |f, i| {
1714 errdefer inline for (info.fields[0..i]) |g| gpa.free(@field(new_items, g.name)[0..actual_new_capacity]);
1715 @field(new_items, f.name) = try gpa.alloc(f.type, actual_new_capacity);
1716 @field(new_items, f.name).len = previous_len;
1717 }
1718 inline for (info.fields) |f| {
1719 @memcpy(@field(new_items, f.name)[0..previous_len], @field(self.items, f.name)[0..previous_len]);
1720 }
1721 inline for (info.fields) |f| {
1722 gpa.free(@field(self.items, f.name)[0..previous_len]);
1723 }
1724 self.items = new_items;
1725 self.capacity = actual_new_capacity;
1726 }
1727
1728 /// Called when memory growth is necessary. Returns a capacity larger than minimum that grows super-linearly.
1729 fn growCapacity(current: usize, minimum: usize) usize {
1730 var new = current;
1731 while (true) {
1732 new +|= new / 2 + init_capacity;
1733 if (new >= minimum) return new;
1734 }
1735 }
1736
1737 const init_capacity = init: {
1738 var max = 1;
1739 for (info.fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type)));
1740 break :init @as(comptime_int, @max(1, std.atomic.cache_line / max));
1741 };
1742
1743 pub fn append(self: *Self, gpa: std.mem.Allocator, elem: T) !void {
1744 try self.ensureUnusedCapacity(gpa, 1);
1745 self.appendAssumeCapacity(elem);
1746 }
1747
1748 pub fn appendAssumeCapacity(self: *Self, elem: T) void {
1749 const plen = self.len();
1750 std.debug.assert(plen < self.capacity);
1751 inline for (info.fields) |f| {
1752 @field(self.items, f.name).len += 1;
1753 @field(self.items, f.name)[plen] = @field(elem, f.name);
1754 }
1755 }
1756 };
1757}