| ... | ... | @@ -270,18 +270,18 @@ fn parseTreeMode(input: string) !Tree.Object.Mode { |
| 270 | 270 | } |
| 271 | 271 | |
| 272 | 272 | pub const Tree = struct { |
| 273 | | children: std.MultiArrayList(Object).Slice, |
| 273 | children: MultiArrayList(Object), |
| 274 | 274 | |
| 275 | 275 | pub fn get(self: Tree, name: string) ?Object { |
| 276 | | if (self.children.len <= 1000) { |
| 277 | | for (self.children.items(.name), 0..) |a_name, i| { |
| 276 | if (self.children.len() <= 1000) { |
| 277 | for (self.children.items.name, 0..) |a_name, i| { |
| 278 | 278 | if (std.mem.eql(u8, a_name, name)) { |
| 279 | 279 | return self.children.get(i); |
| 280 | 280 | } |
| 281 | 281 | } |
| 282 | 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 | 285 | return self.children.get(i); |
| 286 | 286 | } |
| 287 | 287 | |
| ... | ... | @@ -292,7 +292,7 @@ pub const Tree = struct { |
| 292 | 292 | } |
| 293 | 293 | |
| 294 | 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 | 296 | if (std.ascii.eqlIgnoreCase(item, name)) { |
| 297 | 297 | return self.children.get(i); |
| 298 | 298 | } |
| ... | ... | @@ -1196,12 +1196,11 @@ pub const Repository = struct { |
| 1196 | 1196 | } |
| 1197 | 1197 | if (try r.getObject(arena, id.id)) |obj| { |
| 1198 | 1198 | if (obj.type == .tree) { |
| 1199 | | const MAL = std.MultiArrayList(Tree.Object); |
| 1199 | const MAL = MultiArrayList(Tree.Object); |
| 1200 | 1200 | var children: MAL = .empty; |
| 1201 | 1201 | errdefer children.deinit(r.gpa); |
| 1202 | 1202 | try children.ensureUnusedCapacity(r.gpa, 33); |
| 1203 | 1203 | var i: usize = 0; |
| 1204 | | var slice = children.slice(); |
| 1205 | 1204 | while (i < obj.content.len) { |
| 1206 | 1205 | const mode_end = std.mem.indexOfScalar(u8, obj.content[i..], ' ').?; |
| 1207 | 1206 | const mode = obj.content[i..][0..mode_end]; |
| ... | ... | @@ -1220,15 +1219,7 @@ pub const Repository = struct { |
| 1220 | 1219 | @memcpy(mode_buf[6 - mode.len ..], mode); |
| 1221 | 1220 | const mode_real = try parseTreeMode(&mode_buf); |
| 1222 | 1221 | |
| 1223 | | const j = slice.len; |
| 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, .{ |
| 1222 | try children.append(r.gpa, .{ |
| 1232 | 1223 | .mode = mode_real, |
| 1233 | 1224 | .name = name, |
| 1234 | 1225 | .id = switch (mode_real.type) { |
| ... | ... | @@ -1245,21 +1236,20 @@ pub const Repository = struct { |
| 1245 | 1236 | list: *MAL, |
| 1246 | 1237 | |
| 1247 | 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 | 1240 | return std.mem.order(u8, items[a], items[b]) == .lt; |
| 1250 | 1241 | } |
| 1251 | 1242 | pub fn swap(s: @This(), a: usize, b: usize) void { |
| 1252 | 1243 | // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016 |
| 1253 | 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 | 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() }; |
| 1262 | | errdefer tree.children.deinit(r.gpa); |
| 1252 | const tree: Tree = .{ .children = children }; |
| 1263 | 1253 | try r.trees.put(r.gpa, id.id, tree); |
| 1264 | 1254 | return .{ id, tree }; |
| 1265 | 1255 | } |
| ... | ... | @@ -1371,12 +1361,12 @@ pub const Repository = struct { |
| 1371 | 1361 | const base_idx = try r.getCommitA(arena, base_oid.id); |
| 1372 | 1362 | const base = base_idx.reify(r); |
| 1373 | 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 | 1366 | var found: usize = 0; |
| 1377 | 1367 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; |
| 1378 | 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 | 1371 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); |
| 1382 | 1372 | defer set.deinit(r.gpa); |
| ... | ... | @@ -1502,3 +1492,116 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1502 | 1492 | } |
| 1503 | 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 | } |