authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-29 13:36:47-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-29 13:42:38-07:00
log9013970861176717aae3020f3f20434dc5220073
treeb9475f7719505a871234ed1a83ab492c8201962a
parent9d765b5ab5662622e8e2465e930d3905ed4612a4

std.ArrayList: fixups for the previous commit

* Move `computeBetterCapacity` to the bottom so that `pub` stuff shows up first. * Rename `computeBetterCapacity` to `growCapacity`. Every function implicitly computes something; that word is always redundant in a function name. "better" is vague. Better in what way? Instead we describe what is actually happening. "grow". * Improve doc comments to be very explicit about when element pointers are invalidated or not. * Rename `addManyAtIndex` to `addManyAt`. The parameter is named `index`; that is enough. * Extract some duplicated code into `addManyAtAssumeCapacity` and make it `pub`. * Since I audited every line of code for correctness, I changed the style to my personal preference. * Avoid a redundant `@memset` to `undefined` - memory allocation does that already. * Fixed comment giving the wrong reason for not calling `ensureTotalCapacity`.

1 files changed, 107 insertions(+), 98 deletions(-)

lib/std/array_list.zig+107-98
......@@ -6,21 +6,6 @@ const mem = std.mem;
66const math = std.math;
77const Allocator = mem.Allocator;
88
9/// Shared between managed and unmanaged versions of ArrayList. Called
10/// when memory growth is necessary. Returns a capacity larger than minimum
11/// that is better according to our growth policy.
12fn computeBetterCapacity(
13 current_capacity: usize,
14 minimum_capacity: usize,
15) usize {
16 var better_capacity = current_capacity;
17 while (true) {
18 better_capacity +|= better_capacity / 2 + 8;
19 if (better_capacity >= minimum_capacity)
20 return better_capacity;
21 }
22}
23
249/// A contiguous, growable list of items in memory.
2510/// This is a wrapper around an array of T values. Initialize with `init`.
2611///
......@@ -177,91 +162,74 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
177162 self.items[n] = item;
178163 }
179164
180 /// Resize the array, adding `count` new elements at position `index`, which have `undefined` values.
181 /// The return value is a slice pointing to the newly allocated elements. The returned pointer
182 /// becomes invalid when the list is resized. Resizes list if self.capacity is not large enough.
183 pub fn addManyAtIndex(
184 self: *Self,
185 index: usize,
186 count: usize,
187 ) Allocator.Error![]T {
165 /// Add `count` new elements at position `index`, which have
166 /// `undefined` values. Returns a slice pointing to the newly allocated
167 /// elements, which becomes invalid after various `ArrayList`
168 /// operations.
169 /// Invalidates pre-existing pointers to elements at and after `index`.
170 /// Invalidates all pre-existing element pointers if capacity must be
171 /// increased to accomodate the new elements.
172 pub fn addManyAt(self: *Self, index: usize, count: usize) Allocator.Error![]T {
188173 const new_len = self.items.len + count;
189 const to_move = self.items[index..];
190174
191 if (self.capacity >= new_len) {
192 //There is enough space
193 self.items.len = new_len;
194 mem.copyBackwards(
195 T,
196 self.items[index + count ..],
197 to_move,
198 );
199 const result = self.items[index..][0..count];
200 @memset(result, undefined);
201 return result;
202 } else {
203 const better_capacity = computeBetterCapacity(self.capacity, new_len);
204
205 // Here we avoid copying allocated but unused bytes by
206 // attempting a resize in place, and falling back to allocating
207 // a new buffer and doing our own copy. With a realloc() call,
208 // the allocator implementation would pointlessly copy our
209 // extra capacity.
210 const old_memory = self.allocatedSlice();
211 if (self.allocator.resize(old_memory, better_capacity)) {
212 self.capacity = better_capacity;
213 self.items.len = new_len;
214 mem.copyBackwards(
215 T,
216 self.items[index + count ..],
217 to_move,
218 );
219 const result = self.items[index..][0..count];
220 @memset(result, undefined);
221 return result;
222 } else {
223 // Need a new allocation. We don't call ensureTotalCapacity because there
224 // would be an unnecessary check if the capacity is enough (we already
225 // know it's not).
226 const new_memory = try self.allocator.alignedAlloc(
227 T,
228 alignment,
229 better_capacity,
230 );
231 @memcpy(
232 new_memory[0..index],
233 self.items[0..index],
234 );
235
236 // No need to mem.copyBackwards, as this is a new allocation.
237 @memcpy(
238 new_memory[index + count ..][0..to_move.len],
239 to_move,
240 );
241
242 self.allocator.free(old_memory);
243 self.items.ptr = new_memory.ptr;
244 self.items.len = new_len;
245 self.capacity = new_memory.len;
246 const result = new_memory[index..][0..count];
247 @memset(result, undefined);
248 return result;
249 }
175 if (self.capacity >= new_len)
176 return addManyAtAssumeCapacity(self, index, count);
177
178 // Here we avoid copying allocated but unused bytes by
179 // attempting a resize in place, and falling back to allocating
180 // a new buffer and doing our own copy. With a realloc() call,
181 // the allocator implementation would pointlessly copy our
182 // extra capacity.
183 const new_capacity = growCapacity(self.capacity, new_len);
184 const old_memory = self.allocatedSlice();
185 if (self.allocator.resize(old_memory, new_capacity)) {
186 self.capacity = new_capacity;
187 return addManyAtAssumeCapacity(self, index, count);
250188 }
189
190 // Make a new allocation, avoiding `ensureTotalCapacity` in order
191 // to avoid extra memory copies.
192 const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
193 const to_move = self.items[index..];
194 @memcpy(new_memory[0..index], self.items[0..index]);
195 @memcpy(new_memory[index + count ..][0..to_move.len], to_move);
196 self.allocator.free(old_memory);
197 self.items = new_memory[0..new_len];
198 self.capacity = new_memory.len;
199 // The inserted elements at `new_memory[index..][0..count]` have
200 // already been set to `undefined` by memory allocation.
201 return new_memory[index..][0..count];
202 }
203
204 /// Add `count` new elements at position `index`, which have
205 /// `undefined` values. Returns a slice pointing to the newly allocated
206 /// elements, which becomes invalid after various `ArrayList`
207 /// operations.
208 /// Asserts that there is enough capacity for the new elements.
209 /// Invalidates pre-existing pointers to elements at and after `index`, but
210 /// does not invalidate any before that.
211 pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T {
212 const new_len = self.items.len + count;
213 assert(self.capacity >= new_len);
214 const to_move = self.items[index..];
215 self.items.len = new_len;
216 mem.copyBackwards(T, self.items[index + count ..], to_move);
217 const result = self.items[index..][0..count];
218 @memset(result, undefined);
219 return result;
251220 }
252221
253222 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
254223 /// This operation is O(N).
255 /// Invalidates pointers if additional memory is needed.
224 /// Invalidates pre-existing pointers to elements at and after `index`.
225 /// Invalidates all pre-existing element pointers if capacity must be
226 /// increased to accomodate the new elements.
256227 pub fn insertSlice(
257228 self: *Self,
258229 index: usize,
259230 items: []const T,
260231 ) Allocator.Error!void {
261 const dst = try self.addManyAtIndex(
262 index,
263 items.len,
264 );
232 const dst = try self.addManyAt(index, items.len);
265233 @memcpy(dst, items);
266234 }
267235
......@@ -462,7 +430,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
462430
463431 if (self.capacity >= new_capacity) return;
464432
465 const better_capacity = computeBetterCapacity(self.capacity, new_capacity);
433 const better_capacity = growCapacity(self.capacity, new_capacity);
466434 return self.ensureTotalCapacityPrecise(better_capacity);
467435 }
468436
......@@ -750,10 +718,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
750718 self.items[n] = item;
751719 }
752720
753 /// Resize the array, adding `count` new elements at position `index`, which have `undefined` values.
754 /// The return value is a slice pointing to the newly allocated elements. The returned pointer
755 /// becomes invalid when the list is resized. Resizes list if self.capacity is not large enough.
756 pub fn addManyAtIndex(
721 /// Add `count` new elements at position `index`, which have
722 /// `undefined` values. Returns a slice pointing to the newly allocated
723 /// elements, which becomes invalid after various `ArrayList`
724 /// operations.
725 /// Invalidates pre-existing pointers to elements at and after `index`.
726 /// Invalidates all pre-existing element pointers if capacity must be
727 /// increased to accomodate the new elements.
728 pub fn addManyAt(
757729 self: *Self,
758730 allocator: Allocator,
759731 index: usize,
......@@ -761,19 +733,39 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
761733 ) Allocator.Error![]T {
762734 var managed = self.toManaged(allocator);
763735 defer self.* = managed.moveToUnmanaged();
764 return managed.addManyAtIndex(index, count);
736 return managed.addManyAt(index, count);
737 }
738
739 /// Add `count` new elements at position `index`, which have
740 /// `undefined` values. Returns a slice pointing to the newly allocated
741 /// elements, which becomes invalid after various `ArrayList`
742 /// operations.
743 /// Asserts that there is enough capacity for the new elements.
744 /// Invalidates pre-existing pointers to elements at and after `index`, but
745 /// does not invalidate any before that.
746 pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T {
747 const new_len = self.items.len + count;
748 assert(self.capacity >= new_len);
749 const to_move = self.items[index..];
750 self.items.len = new_len;
751 mem.copyBackwards(T, self.items[index + count ..], to_move);
752 const result = self.items[index..][0..count];
753 @memset(result, undefined);
754 return result;
765755 }
766756
767757 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
768758 /// This operation is O(N).
769 /// Invalidates pointers if additional memory is needed.
759 /// Invalidates pre-existing pointers to elements at and after `index`.
760 /// Invalidates all pre-existing element pointers if capacity must be
761 /// increased to accomodate the new elements.
770762 pub fn insertSlice(
771763 self: *Self,
772764 allocator: Allocator,
773765 index: usize,
774766 items: []const T,
775767 ) Allocator.Error!void {
776 const dst = try self.addManyAtIndex(
768 const dst = try self.addManyAt(
777769 allocator,
778770 index,
779771 items.len,
......@@ -785,7 +777,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
785777 /// Grows list if `len < new_items.len`.
786778 /// Shrinks list if `len > new_items.len`
787779 /// Invalidates pointers if this ArrayList is resized.
788 pub fn replaceRange(self: *Self, allocator: Allocator, start: usize, len: usize, new_items: []const T) Allocator.Error!void {
780 pub fn replaceRange(
781 self: *Self,
782 allocator: Allocator,
783 start: usize,
784 len: usize,
785 new_items: []const T,
786 ) Allocator.Error!void {
789787 var managed = self.toManaged(allocator);
790788 defer self.* = managed.moveToUnmanaged();
791789 try managed.replaceRange(start, len, new_items);
......@@ -981,7 +979,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
981979 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
982980 if (self.capacity >= new_capacity) return;
983981
984 var better_capacity = computeBetterCapacity(self.capacity, new_capacity);
982 var better_capacity = growCapacity(self.capacity, new_capacity);
985983 return self.ensureTotalCapacityPrecise(allocator, better_capacity);
986984 }
987985
......@@ -1140,6 +1138,17 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
11401138 };
11411139}
11421140
1141/// Called when memory growth is necessary. Returns a capacity larger than
1142/// minimum that grows super-linearly.
1143fn growCapacity(current: usize, minimum: usize) usize {
1144 var new = current;
1145 while (true) {
1146 new +|= new / 2 + 8;
1147 if (new >= minimum)
1148 return new;
1149 }
1150}
1151
11431152test "std.ArrayList/ArrayListUnmanaged.init" {
11441153 {
11451154 var list = ArrayList(i32).init(testing.allocator);