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;...@@ -6,21 +6,6 @@ const mem = std.mem;
6const math = std.math;6const math = std.math;
7const Allocator = mem.Allocator;7const 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
24/// A contiguous, growable list of items in memory.9/// A contiguous, growable list of items in memory.
25/// This is a wrapper around an array of T values. Initialize with `init`.10/// This is a wrapper around an array of T values. Initialize with `init`.
26///11///
...@@ -177,91 +162,74 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -177,91 +162,74 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
177 self.items[n] = item;162 self.items[n] = item;
178 }163 }
179164
180 /// Resize the array, adding `count` new elements at position `index`, which have `undefined` values.165 /// Add `count` new elements at position `index`, which have
181 /// The return value is a slice pointing to the newly allocated elements. The returned pointer166 /// `undefined` values. Returns a slice pointing to the newly allocated
182 /// becomes invalid when the list is resized. Resizes list if self.capacity is not large enough.167 /// elements, which becomes invalid after various `ArrayList`
183 pub fn addManyAtIndex(168 /// operations.
184 self: *Self,169 /// Invalidates pre-existing pointers to elements at and after `index`.
185 index: usize,170 /// Invalidates all pre-existing element pointers if capacity must be
186 count: usize,171 /// increased to accomodate the new elements.
187 ) Allocator.Error![]T {172 pub fn addManyAt(self: *Self, index: usize, count: usize) Allocator.Error![]T {
188 const new_len = self.items.len + count;173 const new_len = self.items.len + count;
189 const to_move = self.items[index..];
190174
191 if (self.capacity >= new_len) {175 if (self.capacity >= new_len)
192 //There is enough space176 return addManyAtAssumeCapacity(self, index, count);
193 self.items.len = new_len;177
194 mem.copyBackwards(178 // Here we avoid copying allocated but unused bytes by
195 T,179 // attempting a resize in place, and falling back to allocating
196 self.items[index + count ..],180 // a new buffer and doing our own copy. With a realloc() call,
197 to_move,181 // the allocator implementation would pointlessly copy our
198 );182 // extra capacity.
199 const result = self.items[index..][0..count];183 const new_capacity = growCapacity(self.capacity, new_len);
200 @memset(result, undefined);184 const old_memory = self.allocatedSlice();
201 return result;185 if (self.allocator.resize(old_memory, new_capacity)) {
202 } else {186 self.capacity = new_capacity;
203 const better_capacity = computeBetterCapacity(self.capacity, new_len);187 return addManyAtAssumeCapacity(self, index, count);
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 }
250 }188 }
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;
251 }220 }
252221
253 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.222 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
254 /// This operation is O(N).223 /// 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.
256 pub fn insertSlice(227 pub fn insertSlice(
257 self: *Self,228 self: *Self,
258 index: usize,229 index: usize,
259 items: []const T,230 items: []const T,
260 ) Allocator.Error!void {231 ) Allocator.Error!void {
261 const dst = try self.addManyAtIndex(232 const dst = try self.addManyAt(index, items.len);
262 index,
263 items.len,
264 );
265 @memcpy(dst, items);233 @memcpy(dst, items);
266 }234 }
267235
...@@ -462,7 +430,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -462,7 +430,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
462430
463 if (self.capacity >= new_capacity) return;431 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);
466 return self.ensureTotalCapacityPrecise(better_capacity);434 return self.ensureTotalCapacityPrecise(better_capacity);
467 }435 }
468436
...@@ -750,10 +718,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -750,10 +718,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
750 self.items[n] = item;718 self.items[n] = item;
751 }719 }
752720
753 /// Resize the array, adding `count` new elements at position `index`, which have `undefined` values.721 /// Add `count` new elements at position `index`, which have
754 /// The return value is a slice pointing to the newly allocated elements. The returned pointer722 /// `undefined` values. Returns a slice pointing to the newly allocated
755 /// becomes invalid when the list is resized. Resizes list if self.capacity is not large enough.723 /// elements, which becomes invalid after various `ArrayList`
756 pub fn addManyAtIndex(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(
757 self: *Self,729 self: *Self,
758 allocator: Allocator,730 allocator: Allocator,
759 index: usize,731 index: usize,
...@@ -761,19 +733,39 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -761,19 +733,39 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
761 ) Allocator.Error![]T {733 ) Allocator.Error![]T {
762 var managed = self.toManaged(allocator);734 var managed = self.toManaged(allocator);
763 defer self.* = managed.moveToUnmanaged();735 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;
765 }755 }
766756
767 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.757 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
768 /// This operation is O(N).758 /// 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.
770 pub fn insertSlice(762 pub fn insertSlice(
771 self: *Self,763 self: *Self,
772 allocator: Allocator,764 allocator: Allocator,
773 index: usize,765 index: usize,
774 items: []const T,766 items: []const T,
775 ) Allocator.Error!void {767 ) Allocator.Error!void {
776 const dst = try self.addManyAtIndex(768 const dst = try self.addManyAt(
777 allocator,769 allocator,
778 index,770 index,
779 items.len,771 items.len,
...@@ -785,7 +777,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -785,7 +777,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
785 /// Grows list if `len < new_items.len`.777 /// Grows list if `len < new_items.len`.
786 /// Shrinks list if `len > new_items.len`778 /// Shrinks list if `len > new_items.len`
787 /// Invalidates pointers if this ArrayList is resized.779 /// 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 {
789 var managed = self.toManaged(allocator);787 var managed = self.toManaged(allocator);
790 defer self.* = managed.moveToUnmanaged();788 defer self.* = managed.moveToUnmanaged();
791 try managed.replaceRange(start, len, new_items);789 try managed.replaceRange(start, len, new_items);
...@@ -981,7 +979,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -981,7 +979,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
981 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {979 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
982 if (self.capacity >= new_capacity) return;980 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);
985 return self.ensureTotalCapacityPrecise(allocator, better_capacity);983 return self.ensureTotalCapacityPrecise(allocator, better_capacity);
986 }984 }
987985
...@@ -1140,6 +1138,17 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -1140,6 +1138,17 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
1140 };1138 };
1141}1139}
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
1143test "std.ArrayList/ArrayListUnmanaged.init" {1152test "std.ArrayList/ArrayListUnmanaged.init" {
1144 {1153 {
1145 var list = ArrayList(i32).init(testing.allocator);1154 var list = ArrayList(i32).init(testing.allocator);