authorgravatar for lucascarvalhosantos91@gmail.comLucas Santos <lucascarvalhosantos91@gmail.com> 2023-09-27 21:20:34-03:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-29 12:52:40-07:00
log9d765b5ab5662622e8e2465e930d3905ed4612a4
treee88b93e9891a10f0b39d8d42ca8d000d29317cbf
parente919fbea9fd62e55ebbfb0739a2a468c93673e93

std.ArrayList: insertSlice avoids extra memcpy

Includes a more robust implementation of replaceRange, which updates the ArrayListUnmanaged if state changes in the managed part of the code before returning an error. Co-authored-by: Andrew Kelley <andrew@ziglang.org>

1 files changed, 162 insertions(+), 27 deletions(-)

lib/std/array_list.zig+162-27
...@@ -6,6 +6,21 @@ const mem = std.mem;...@@ -6,6 +6,21 @@ 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
9/// A contiguous, growable list of items in memory.24/// A contiguous, growable list of items in memory.
10/// This is a wrapper around an array of T values. Initialize with `init`.25/// This is a wrapper around an array of T values. Initialize with `init`.
11///26///
...@@ -162,15 +177,92 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -162,15 +177,92 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
162 self.items[n] = item;177 self.items[n] = item;
163 }178 }
164179
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 {
188 const new_len = self.items.len + count;
189 const to_move = self.items[index..];
190
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 }
250 }
251 }
252
165 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.253 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
166 /// This operation is O(N).254 /// This operation is O(N).
167 /// Invalidates pointers if additional memory is needed.255 /// Invalidates pointers if additional memory is needed.
168 pub fn insertSlice(self: *Self, i: usize, items: []const T) Allocator.Error!void {256 pub fn insertSlice(
169 try self.ensureUnusedCapacity(items.len);257 self: *Self,
170 self.items.len += items.len;258 index: usize,
171259 items: []const T,
172 mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]);260 ) Allocator.Error!void {
173 @memcpy(self.items[i..][0..items.len], items);261 const dst = try self.addManyAtIndex(
262 index,
263 items.len,
264 );
265 @memcpy(dst, items);
174 }266 }
175267
176 /// Replace range of elements `list[start..][0..len]` with `new_items`.268 /// Replace range of elements `list[start..][0..len]` with `new_items`.
...@@ -370,12 +462,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -370,12 +462,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
370462
371 if (self.capacity >= new_capacity) return;463 if (self.capacity >= new_capacity) return;
372464
373 var better_capacity = self.capacity;465 const better_capacity = computeBetterCapacity(self.capacity, new_capacity);
374 while (true) {
375 better_capacity +|= better_capacity / 2 + 8;
376 if (better_capacity >= new_capacity) break;
377 }
378
379 return self.ensureTotalCapacityPrecise(better_capacity);466 return self.ensureTotalCapacityPrecise(better_capacity);
380 }467 }
381468
...@@ -663,16 +750,35 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -663,16 +750,35 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
663 self.items[n] = item;750 self.items[n] = item;
664 }751 }
665752
666 /// Insert slice `items` at index `i`. Moves `list[i .. list.len]` to753 /// Resize the array, adding `count` new elements at position `index`, which have `undefined` values.
667 /// higher indicices make room.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(
757 self: *Self,
758 allocator: Allocator,
759 index: usize,
760 count: usize,
761 ) Allocator.Error![]T {
762 var managed = self.toManaged(allocator);
763 defer self.* = managed.moveToUnmanaged();
764 return managed.addManyAtIndex(index, count);
765 }
766
767 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
668 /// This operation is O(N).768 /// This operation is O(N).
669 /// Invalidates pointers if additional memory is needed.769 /// Invalidates pointers if additional memory is needed.
670 pub fn insertSlice(self: *Self, allocator: Allocator, i: usize, items: []const T) Allocator.Error!void {770 pub fn insertSlice(
671 try self.ensureUnusedCapacity(allocator, items.len);771 self: *Self,
672 self.items.len += items.len;772 allocator: Allocator,
673773 index: usize,
674 mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]);774 items: []const T,
675 @memcpy(self.items[i..][0..items.len], items);775 ) Allocator.Error!void {
776 const dst = try self.addManyAtIndex(
777 allocator,
778 index,
779 items.len,
780 );
781 @memcpy(dst, items);
676 }782 }
677783
678 /// Replace range of elements `list[start..][0..len]` with `new_items`784 /// Replace range of elements `list[start..][0..len]` with `new_items`
...@@ -681,8 +787,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -681,8 +787,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
681 /// Invalidates pointers if this ArrayList is resized.787 /// Invalidates pointers if this ArrayList is resized.
682 pub fn replaceRange(self: *Self, allocator: Allocator, start: usize, len: usize, new_items: []const T) Allocator.Error!void {788 pub fn replaceRange(self: *Self, allocator: Allocator, start: usize, len: usize, new_items: []const T) Allocator.Error!void {
683 var managed = self.toManaged(allocator);789 var managed = self.toManaged(allocator);
790 defer self.* = managed.moveToUnmanaged();
684 try managed.replaceRange(start, len, new_items);791 try managed.replaceRange(start, len, new_items);
685 self.* = managed.moveToUnmanaged();
686 }792 }
687793
688 /// Extend the list by 1 element. Allocates more memory as necessary.794 /// Extend the list by 1 element. Allocates more memory as necessary.
...@@ -875,12 +981,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -875,12 +981,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
875 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {981 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
876 if (self.capacity >= new_capacity) return;982 if (self.capacity >= new_capacity) return;
877983
878 var better_capacity = self.capacity;984 var better_capacity = computeBetterCapacity(self.capacity, new_capacity);
879 while (true) {
880 better_capacity +|= better_capacity / 2 + 8;
881 if (better_capacity >= new_capacity) break;
882 }
883
884 return self.ensureTotalCapacityPrecise(allocator, better_capacity);985 return self.ensureTotalCapacityPrecise(allocator, better_capacity);
885 }986 }
886987
...@@ -1650,6 +1751,40 @@ test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" {...@@ -1650,6 +1751,40 @@ test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" {
1650 }1751 }
1651}1752}
16521753
1754test "std.ArrayList/ArrayListUnmanaged growing memory preserves contents" {
1755 const a = std.testing.allocator;
1756 {
1757 var list = ArrayList(u8).init(a);
1758 defer list.deinit();
1759 try list.ensureTotalCapacityPrecise(1);
1760
1761 (try list.addManyAsArray(4)).* = "abcd".*;
1762 try list.ensureTotalCapacityPrecise(4);
1763
1764 try list.appendSlice("efgh");
1765 try testing.expectEqualSlices(u8, list.items, "abcdefgh");
1766 try list.ensureTotalCapacityPrecise(8);
1767
1768 try list.insertSlice(4, "ijkl");
1769 try testing.expectEqualSlices(u8, list.items, "abcdijklefgh");
1770 }
1771 {
1772 var list = ArrayListUnmanaged(u8){};
1773 try list.ensureTotalCapacityPrecise(a, 1);
1774 defer list.deinit(a);
1775
1776 (try list.addManyAsArray(a, 4)).* = "abcd".*;
1777 try list.ensureTotalCapacityPrecise(a, 4);
1778
1779 try list.appendSlice(a, "efgh");
1780 try testing.expectEqualSlices(u8, list.items, "abcdefgh");
1781 try list.ensureTotalCapacityPrecise(a, 8);
1782
1783 try list.insertSlice(a, 4, "ijkl");
1784 try testing.expectEqualSlices(u8, list.items, "abcdijklefgh");
1785 }
1786}
1787
1653test "std.ArrayList/ArrayList.fromOwnedSliceSentinel" {1788test "std.ArrayList/ArrayList.fromOwnedSliceSentinel" {
1654 const a = testing.allocator;1789 const a = testing.allocator;
16551790