| ... | @@ -270,18 +270,18 @@ fn parseTreeMode(input: string) !Tree.Object.Mode { | ... | @@ -270,18 +270,18 @@ fn parseTreeMode(input: string) !Tree.Object.Mode { |
| 270 | } | 270 | } |
| 271 | | 271 | |
| 272 | pub const Tree = struct { | 272 | pub const Tree = struct { |
| 273 | children: std.MultiArrayList(Object).Slice, | 273 | children: MultiArrayList(Object), |
| 274 | | 274 | |
| 275 | pub fn get(self: Tree, name: string) ?Object { | 275 | pub fn get(self: Tree, name: string) ?Object { |
| 276 | if (self.children.len <= 1000) { | 276 | if (self.children.len() <= 1000) { |
| 277 | for (self.children.items(.name), 0..) |a_name, i| { | 277 | for (self.children.items.name, 0..) |a_name, i| { |
| 278 | if (std.mem.eql(u8, a_name, name)) { | 278 | if (std.mem.eql(u8, a_name, name)) { |
| 279 | return self.children.get(i); | 279 | return self.children.get(i); |
| 280 | } | 280 | } |
| 281 | } | 281 | } |
| 282 | return null; | 282 | return null; |
| 283 | } | 283 | } |
| 284 | const i = std.sort.binarySearch([]const u8, self.children.items(.name), name, extras.compareFnSlice(u8)) orelse return null; | 284 | const i = std.sort.binarySearch([]const u8, self.children.items.name, name, extras.compareFnSlice(u8)) orelse return null; |
| 285 | return self.children.get(i); | 285 | return self.children.get(i); |
| 286 | } | 286 | } |
| 287 | | 287 | |
| ... | @@ -292,7 +292,7 @@ pub const Tree = struct { | ... | @@ -292,7 +292,7 @@ pub const Tree = struct { |
| 292 | } | 292 | } |
| 293 | | 293 | |
| 294 | pub fn find(self: Tree, name: string) ?Object { | 294 | pub fn find(self: Tree, name: string) ?Object { |
| 295 | for (self.children.items(.name), 0..) |item, i| { | 295 | for (self.children.items.name, 0..) |item, i| { |
| 296 | if (std.ascii.eqlIgnoreCase(item, name)) { | 296 | if (std.ascii.eqlIgnoreCase(item, name)) { |
| 297 | return self.children.get(i); | 297 | return self.children.get(i); |
| 298 | } | 298 | } |
| ... | @@ -1196,12 +1196,11 @@ pub const Repository = struct { | ... | @@ -1196,12 +1196,11 @@ pub const Repository = struct { |
| 1196 | } | 1196 | } |
| 1197 | if (try r.getObject(arena, id.id)) |obj| { | 1197 | if (try r.getObject(arena, id.id)) |obj| { |
| 1198 | if (obj.type == .tree) { | 1198 | if (obj.type == .tree) { |
| 1199 | const MAL = std.MultiArrayList(Tree.Object); | 1199 | const MAL = MultiArrayList(Tree.Object); |
| 1200 | var children: MAL = .empty; | 1200 | var children: MAL = .empty; |
| 1201 | errdefer children.deinit(r.gpa); | 1201 | errdefer children.deinit(r.gpa); |
| 1202 | try children.ensureUnusedCapacity(r.gpa, 33); | 1202 | try children.ensureUnusedCapacity(r.gpa, 33); |
| 1203 | var i: usize = 0; | 1203 | var i: usize = 0; |
| 1204 | var slice = children.slice(); | | |
| 1205 | while (i < obj.content.len) { | 1204 | while (i < obj.content.len) { |
| 1206 | const mode_end = std.mem.indexOfScalar(u8, obj.content[i..], ' ').?; | 1205 | const mode_end = std.mem.indexOfScalar(u8, obj.content[i..], ' ').?; |
| 1207 | const mode = obj.content[i..][0..mode_end]; | 1206 | const mode = obj.content[i..][0..mode_end]; |
| ... | @@ -1220,15 +1219,7 @@ pub const Repository = struct { | ... | @@ -1220,15 +1219,7 @@ pub const Repository = struct { |
| 1220 | @memcpy(mode_buf[6 - mode.len ..], mode); | 1219 | @memcpy(mode_buf[6 - mode.len ..], mode); |
| 1221 | const mode_real = try parseTreeMode(&mode_buf); | 1220 | const mode_real = try parseTreeMode(&mode_buf); |
| 1222 | | 1221 | |
| 1223 | const j = slice.len; | 1222 | try children.append(r.gpa, .{ |
| 1224 | if (j == slice.capacity) { | | |
| 1225 | @branchHint(.cold); | | |
| 1226 | try children.ensureUnusedCapacity(r.gpa, 1); | | |
| 1227 | slice = children.slice(); | | |
| 1228 | } | | |
| 1229 | children.len += 1; | | |
| 1230 | slice.len += 1; | | |
| 1231 | slice.set(j, .{ | | |
| 1232 | .mode = mode_real, | 1223 | .mode = mode_real, |
| 1233 | .name = name, | 1224 | .name = name, |
| 1234 | .id = switch (mode_real.type) { | 1225 | .id = switch (mode_real.type) { |
| ... | @@ -1245,21 +1236,20 @@ pub const Repository = struct { | ... | @@ -1245,21 +1236,20 @@ pub const Repository = struct { |
| 1245 | list: *MAL, | 1236 | list: *MAL, |
| 1246 | | 1237 | |
| 1247 | pub fn lessThan(s: @This(), a: usize, b: usize) bool { | 1238 | pub fn lessThan(s: @This(), a: usize, b: usize) bool { |
| 1248 | const items = s.list.items(.name); | 1239 | const items = s.list.items.name; |
| 1249 | return std.mem.order(u8, items[a], items[b]) == .lt; | 1240 | return std.mem.order(u8, items[a], items[b]) == .lt; |
| 1250 | } | 1241 | } |
| 1251 | pub fn swap(s: @This(), a: usize, b: usize) void { | 1242 | pub fn swap(s: @This(), a: usize, b: usize) void { |
| 1252 | // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016 | 1243 | // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016 |
| 1253 | inline for (@typeInfo(Tree.Object).@"struct".fields) |field| { | 1244 | inline for (@typeInfo(Tree.Object).@"struct".fields) |field| { |
| 1254 | const its = s.list.items(@field(MAL.Field, field.name)); | 1245 | const its = @field(s.list.items, field.name); |
| 1255 | std.mem.swap(@FieldType(Tree.Object, field.name), &its[a], &its[b]); | 1246 | std.mem.swap(@FieldType(Tree.Object, field.name), &its[a], &its[b]); |
| 1256 | } | 1247 | } |
| 1257 | } | 1248 | } |
| 1258 | }; | 1249 | }; |
| 1259 | if (children.len > 1000) std.mem.sortContext(0, children.len, S{ .list = &children }); | 1250 | if (children.len() > 1000) std.mem.sortContext(0, children.len(), S{ .list = &children }); |
| 1260 | | 1251 | |
| 1261 | var tree: Tree = .{ .children = children.toOwnedSlice() }; | 1252 | const tree: Tree = .{ .children = children }; |
| 1262 | errdefer tree.children.deinit(r.gpa); | | |
| 1263 | try r.trees.put(r.gpa, id.id, tree); | 1253 | try r.trees.put(r.gpa, id.id, tree); |
| 1264 | return .{ id, tree }; | 1254 | return .{ id, tree }; |
| 1265 | } | 1255 | } |
| ... | @@ -1371,12 +1361,12 @@ pub const Repository = struct { | ... | @@ -1371,12 +1361,12 @@ pub const Repository = struct { |
| 1371 | const base_idx = try r.getCommitA(arena, base_oid.id); | 1361 | const base_idx = try r.getCommitA(arena, base_oid.id); |
| 1372 | const base = base_idx.reify(r); | 1362 | const base = base_idx.reify(r); |
| 1373 | const base_tree = (try traverseTo(r, arena, base.tree, dir_path)).?; | 1363 | const base_tree = (try traverseTo(r, arena, base.tree, dir_path)).?; |
| 1374 | const total = base_tree.children.len; | 1364 | const total = base_tree.children.len(); |
| 1375 | | 1365 | |
| 1376 | var found: usize = 0; | 1366 | var found: usize = 0; |
| 1377 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; | 1367 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; |
| 1378 | defer result.deinit(r.gpa); | 1368 | defer result.deinit(r.gpa); |
| 1379 | for (base_tree.children.items(.name)) |name| try result.put(r.gpa, name, undefined); | 1369 | for (base_tree.children.items.name) |name| try result.put(r.gpa, name, undefined); |
| 1380 | | 1370 | |
| 1381 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); | 1371 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); |
| 1382 | defer set.deinit(r.gpa); | 1372 | defer set.deinit(r.gpa); |
| ... | @@ -1502,3 +1492,116 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di | ... | @@ -1502,3 +1492,116 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1502 | } | 1492 | } |
| 1503 | return tree; | 1493 | return tree; |
| 1504 | } | 1494 | } |
| | 1495 | |
| | 1496 | /// Variant on std.MultiArrayList but using an allocation per-field rather than a single for the entire structure. |
| | 1497 | fn MultiArrayList(T: type) type { |
| | 1498 | return struct { |
| | 1499 | items: Items, |
| | 1500 | capacity: usize, |
| | 1501 | |
| | 1502 | comptime Elem: type = T, |
| | 1503 | |
| | 1504 | pub const empty: Self = .{ |
| | 1505 | .items = .{}, |
| | 1506 | .capacity = 0, |
| | 1507 | }; |
| | 1508 | |
| | 1509 | pub const Self = @This(); |
| | 1510 | |
| | 1511 | const info = @typeInfo(T).@"struct"; |
| | 1512 | |
| | 1513 | pub const Items = blk: { |
| | 1514 | var fields: [info.fields.len]std.builtin.Type.StructField = undefined; |
| | 1515 | for (info.fields, 0..) |f, i| { |
| | 1516 | const empty_slice: []f.type = &[_]f.type{}; |
| | 1517 | fields[i] = .{ |
| | 1518 | .name = f.name, |
| | 1519 | .type = []f.type, |
| | 1520 | .default_value_ptr = @ptrCast(&empty_slice), |
| | 1521 | .is_comptime = false, |
| | 1522 | .alignment = @alignOf(f.type), |
| | 1523 | }; |
| | 1524 | } |
| | 1525 | const _fields = fields; |
| | 1526 | break :blk @Type(.{ |
| | 1527 | .@"struct" = .{ |
| | 1528 | .layout = .auto, |
| | 1529 | .fields = &_fields, |
| | 1530 | .decls = &.{}, |
| | 1531 | .is_tuple = false, |
| | 1532 | }, |
| | 1533 | }); |
| | 1534 | }; |
| | 1535 | |
| | 1536 | pub fn deinit(self: *const Self, gpa: std.mem.Allocator) void { |
| | 1537 | inline for (info.fields) |f| { |
| | 1538 | gpa.free(@field(self.items, f.name).ptr[0..self.capacity]); |
| | 1539 | } |
| | 1540 | } |
| | 1541 | |
| | 1542 | pub fn len(self: *const Self) usize { |
| | 1543 | return @field(self.items, info.fields[0].name).len; |
| | 1544 | } |
| | 1545 | |
| | 1546 | pub fn get(self: *const Self, index: usize) T { |
| | 1547 | var result: T = undefined; |
| | 1548 | inline for (info.fields) |f| { |
| | 1549 | @field(result, f.name) = @field(self.items, f.name)[index]; |
| | 1550 | } |
| | 1551 | return result; |
| | 1552 | } |
| | 1553 | |
| | 1554 | pub fn ensureUnusedCapacity(self: *Self, gpa: std.mem.Allocator, additional_count: usize) !void { |
| | 1555 | return self.ensureTotalCapacity(gpa, self.len() + additional_count); |
| | 1556 | } |
| | 1557 | |
| | 1558 | pub fn ensureTotalCapacity(self: *Self, gpa: std.mem.Allocator, new_capacity: usize) !void { |
| | 1559 | if (self.capacity >= new_capacity) return; |
| | 1560 | const actual_new_capacity = growCapacity(self.capacity, new_capacity); |
| | 1561 | const previous_len = self.len(); |
| | 1562 | var new_items: Items = .{}; |
| | 1563 | inline for (info.fields, 0..) |f, i| { |
| | 1564 | errdefer inline for (info.fields[0..i]) |g| gpa.free(@field(new_items, g.name)[0..actual_new_capacity]); |
| | 1565 | @field(new_items, f.name) = try gpa.alloc(f.type, actual_new_capacity); |
| | 1566 | @field(new_items, f.name).len = previous_len; |
| | 1567 | } |
| | 1568 | inline for (info.fields) |f| { |
| | 1569 | @memcpy(@field(new_items, f.name)[0..previous_len], @field(self.items, f.name)[0..previous_len]); |
| | 1570 | } |
| | 1571 | inline for (info.fields) |f| { |
| | 1572 | gpa.free(@field(self.items, f.name)[0..previous_len]); |
| | 1573 | } |
| | 1574 | self.items = new_items; |
| | 1575 | self.capacity = actual_new_capacity; |
| | 1576 | } |
| | 1577 | |
| | 1578 | /// Called when memory growth is necessary. Returns a capacity larger than minimum that grows super-linearly. |
| | 1579 | fn growCapacity(current: usize, minimum: usize) usize { |
| | 1580 | var new = current; |
| | 1581 | while (true) { |
| | 1582 | new +|= new / 2 + init_capacity; |
| | 1583 | if (new >= minimum) return new; |
| | 1584 | } |
| | 1585 | } |
| | 1586 | |
| | 1587 | const init_capacity = init: { |
| | 1588 | var max = 1; |
| | 1589 | for (info.fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type))); |
| | 1590 | break :init @as(comptime_int, @max(1, std.atomic.cache_line / max)); |
| | 1591 | }; |
| | 1592 | |
| | 1593 | pub fn append(self: *Self, gpa: std.mem.Allocator, elem: T) !void { |
| | 1594 | try self.ensureUnusedCapacity(gpa, 1); |
| | 1595 | self.appendAssumeCapacity(elem); |
| | 1596 | } |
| | 1597 | |
| | 1598 | pub fn appendAssumeCapacity(self: *Self, elem: T) void { |
| | 1599 | const plen = self.len(); |
| | 1600 | std.debug.assert(plen < self.capacity); |
| | 1601 | inline for (info.fields) |f| { |
| | 1602 | @field(self.items, f.name).len += 1; |
| | 1603 | @field(self.items, f.name)[plen] = @field(elem, f.name); |
| | 1604 | } |
| | 1605 | } |
| | 1606 | }; |
| | 1607 | } |