From f49d42729a22846ec54b6610db415ac8cdaa31db Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 1 Nov 2021 00:38:11 -0700 Subject: [PATCH 1/2] std.ArrayList: add ensureTotalCapacityPrecise and update doc comments initCapacity did and still does use the ensureTotalCapacityPrecise logic because the initial capacity of an ArrayList is not important in terms of how it grows, so allocating a more exact slice up-front allows for saving memory when the array list never exceeds that initial allocation size. There are use cases where this precise capacity is useful outside of the `init` function, though, like in instances where the user does not call the `init` function themselves but otherwise knows that an ArrayList is empty so calling `ensureTotalCapacityPrecise` can give the same memory savings that `initCapacity` would have. Closes #9775 --- lib/std/array_list.zig | 47 +++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 77ba0646cf..8dbd9f8dae 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -56,19 +56,11 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Initialize with capacity to hold at least `num` elements. + /// The resulting capacity is likely to be equal to `num`. /// Deinitialize with `deinit` or use `toOwnedSlice`. pub fn initCapacity(allocator: *Allocator, num: usize) !Self { var self = Self.init(allocator); - - if (@sizeOf(T) > 0) { - const new_memory = try self.allocator.allocAdvanced(T, alignment, num, .at_least); - self.items.ptr = new_memory.ptr; - self.capacity = new_memory.len; - } else { - // If `T` is a zero-sized type, then we do not need to allocate memory. - self.capacity = std.math.maxInt(usize); - } - + try self.ensureTotalCapacityPrecise(num); return self; } @@ -330,8 +322,22 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { if (better_capacity >= new_capacity) break; } + return self.ensureTotalCapacityPrecise(better_capacity); + } else { + self.capacity = std.math.maxInt(usize); + } + } + + /// Modify the array so that it can hold at least `new_capacity` items. + /// Like `ensureTotalCapacity`, but the resulting capacity is much more likely + /// (but not guaranteed) to be equal to `new_capacity`. + /// Invalidates pointers if additional memory is needed. + pub fn ensureTotalCapacityPrecise(self: *Self, new_capacity: usize) !void { + if (@sizeOf(T) > 0) { + if (self.capacity >= new_capacity) return; + // TODO This can be optimized to avoid needlessly copying undefined memory. - const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); + const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), new_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } else { @@ -464,14 +470,11 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ pub const Slice = if (alignment) |a| ([]align(a) T) else []T; /// Initialize with capacity to hold at least num elements. + /// The resulting capacity is likely to be equal to `num`. /// Deinitialize with `deinit` or use `toOwnedSlice`. pub fn initCapacity(allocator: *Allocator, num: usize) !Self { var self = Self{}; - - const new_memory = try allocator.allocAdvanced(T, alignment, num, .at_least); - self.items.ptr = new_memory.ptr; - self.capacity = new_memory.len; - + try self.ensureTotalCapacityPrecise(allocator, num); return self; } @@ -685,7 +688,17 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ if (better_capacity >= new_capacity) break; } - const new_memory = try allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); + return self.ensureTotalCapacityPrecise(allocator, better_capacity); + } + + /// Modify the array so that it can hold at least `new_capacity` items. + /// Like `ensureTotalCapacity`, but the resulting capacity is much more likely + /// (but not guaranteed) to be equal to `new_capacity`. + /// Invalidates pointers if additional memory is needed. + pub fn ensureTotalCapacityPrecise(self: *Self, allocator: *Allocator, new_capacity: usize) !void { + if (self.capacity >= new_capacity) return; + + const new_memory = try allocator.reallocAtLeast(self.allocatedSlice(), new_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } From a34375814106dbc0e0181bca7cc4ffb1821cb51e Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 1 Nov 2021 00:56:55 -0700 Subject: [PATCH 2/2] Update ensureTotalCapacity to ensureTotalCapacityPrecise where it makes sense These calls are all late-initialization of ArrayList's that were initialized outside the current scope. This allows us to still get the potential memory-saving benefits of the 'precision' of initCapacity. --- lib/std/coff.zig | 4 ++-- src/Module.zig | 2 +- src/link/MachO/CodeSignature.zig | 2 +- src/link/MachO/commands.zig | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index c3823f6c53..961cd8ade6 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -276,7 +276,7 @@ pub const Coff = struct { if (self.sections.items.len == self.coff_header.number_of_sections) return; - try self.sections.ensureTotalCapacity(self.coff_header.number_of_sections); + try self.sections.ensureTotalCapacityPrecise(self.coff_header.number_of_sections); const in = self.in_file.reader(); @@ -297,7 +297,7 @@ pub const Coff = struct { std.mem.set(u8, name[8..], 0); } - try self.sections.append(Section{ + self.sections.appendAssumeCapacity(Section{ .header = SectionHeader{ .name = name, .misc = SectionHeader.Misc{ .virtual_size = try in.readIntLittle(u32) }, diff --git a/src/Module.zig b/src/Module.zig index 1f394a0833..c975fc1c36 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4146,7 +4146,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se // for the runtime ones. const fn_ty = decl.ty; const runtime_params_len = @intCast(u32, fn_ty.fnParamLen()); - try inner_block.instructions.ensureTotalCapacity(gpa, runtime_params_len); + try inner_block.instructions.ensureTotalCapacityPrecise(gpa, runtime_params_len); try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 845122f5e3..18c85eb8f7 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -102,7 +102,7 @@ pub fn calcAdhocSignature( var buffer = try allocator.alloc(u8, page_size); defer allocator.free(buffer); - try cdir.data.ensureTotalCapacity(allocator, total_pages * hash_size + id.len + 1); + try cdir.data.ensureTotalCapacityPrecise(allocator, total_pages * hash_size + id.len + 1); // 1. Save the identifier and update offsets cdir.inner.identOffset = cdir.inner.length; diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index 35512886d4..b3a96c8047 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -223,7 +223,7 @@ pub const SegmentCommand = struct { var segment = SegmentCommand{ .inner = inner, }; - try segment.sections.ensureTotalCapacity(alloc, inner.nsects); + try segment.sections.ensureTotalCapacityPrecise(alloc, inner.nsects); var i: usize = 0; while (i < inner.nsects) : (i += 1) {