authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-29 18:15:24-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-09-29 18:15:24-07:00
log101df768a06ef85753efdd6dc558bca68d50d1a5
treec2591c8cd1b6ee700d66063c9f69605afdb077ef
parent19a82ffdba15c9082b5cd854c2c15f7e9c8ecd05
parent9013970861176717aae3020f3f20434dc5220073
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17312 from LucasSantos91/master

Fix inefficiency with ArrayList.insertSlice

1 files changed, 174 insertions(+), 30 deletions(-)

lib/std/array_list.zig+174-30
......@@ -162,15 +162,75 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
162162 self.items[n] = item;
163163 }
164164
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 {
173 const new_len = self.items.len + count;
174
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);
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;
220 }
221
165222 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
166223 /// This operation is O(N).
167 /// Invalidates pointers if additional memory is needed.
168 pub fn insertSlice(self: *Self, i: usize, items: []const T) Allocator.Error!void {
169 try self.ensureUnusedCapacity(items.len);
170 self.items.len += items.len;
171
172 mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]);
173 @memcpy(self.items[i..][0..items.len], items);
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.
227 pub fn insertSlice(
228 self: *Self,
229 index: usize,
230 items: []const T,
231 ) Allocator.Error!void {
232 const dst = try self.addManyAt(index, items.len);
233 @memcpy(dst, items);
174234 }
175235
176236 /// Replace range of elements `list[start..][0..len]` with `new_items`.
......@@ -370,12 +430,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
370430
371431 if (self.capacity >= new_capacity) return;
372432
373 var better_capacity = self.capacity;
374 while (true) {
375 better_capacity +|= better_capacity / 2 + 8;
376 if (better_capacity >= new_capacity) break;
377 }
378
433 const better_capacity = growCapacity(self.capacity, new_capacity);
379434 return self.ensureTotalCapacityPrecise(better_capacity);
380435 }
381436
......@@ -663,26 +718,75 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
663718 self.items[n] = item;
664719 }
665720
666 /// Insert slice `items` at index `i`. Moves `list[i .. list.len]` to
667 /// higher indicices make room.
668 /// This operation is O(N).
669 /// Invalidates pointers if additional memory is needed.
670 pub fn insertSlice(self: *Self, allocator: Allocator, i: usize, items: []const T) Allocator.Error!void {
671 try self.ensureUnusedCapacity(allocator, items.len);
672 self.items.len += items.len;
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(
729 self: *Self,
730 allocator: Allocator,
731 index: usize,
732 count: usize,
733 ) Allocator.Error![]T {
734 var managed = self.toManaged(allocator);
735 defer self.* = managed.moveToUnmanaged();
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;
755 }
673756
674 mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]);
675 @memcpy(self.items[i..][0..items.len], items);
757 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
758 /// This operation is O(N).
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.
762 pub fn insertSlice(
763 self: *Self,
764 allocator: Allocator,
765 index: usize,
766 items: []const T,
767 ) Allocator.Error!void {
768 const dst = try self.addManyAt(
769 allocator,
770 index,
771 items.len,
772 );
773 @memcpy(dst, items);
676774 }
677775
678776 /// Replace range of elements `list[start..][0..len]` with `new_items`
679777 /// Grows list if `len < new_items.len`.
680778 /// Shrinks list if `len > new_items.len`
681779 /// 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 {
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 {
683787 var managed = self.toManaged(allocator);
788 defer self.* = managed.moveToUnmanaged();
684789 try managed.replaceRange(start, len, new_items);
685 self.* = managed.moveToUnmanaged();
686790 }
687791
688792 /// Extend the list by 1 element. Allocates more memory as necessary.
......@@ -875,12 +979,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
875979 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
876980 if (self.capacity >= new_capacity) return;
877981
878 var better_capacity = self.capacity;
879 while (true) {
880 better_capacity +|= better_capacity / 2 + 8;
881 if (better_capacity >= new_capacity) break;
882 }
883
982 var better_capacity = growCapacity(self.capacity, new_capacity);
884983 return self.ensureTotalCapacityPrecise(allocator, better_capacity);
885984 }
886985
......@@ -1039,6 +1138,17 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
10391138 };
10401139}
10411140
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
10421152test "std.ArrayList/ArrayListUnmanaged.init" {
10431153 {
10441154 var list = ArrayList(i32).init(testing.allocator);
......@@ -1650,6 +1760,40 @@ test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" {
16501760 }
16511761}
16521762
1763test "std.ArrayList/ArrayListUnmanaged growing memory preserves contents" {
1764 const a = std.testing.allocator;
1765 {
1766 var list = ArrayList(u8).init(a);
1767 defer list.deinit();
1768 try list.ensureTotalCapacityPrecise(1);
1769
1770 (try list.addManyAsArray(4)).* = "abcd".*;
1771 try list.ensureTotalCapacityPrecise(4);
1772
1773 try list.appendSlice("efgh");
1774 try testing.expectEqualSlices(u8, list.items, "abcdefgh");
1775 try list.ensureTotalCapacityPrecise(8);
1776
1777 try list.insertSlice(4, "ijkl");
1778 try testing.expectEqualSlices(u8, list.items, "abcdijklefgh");
1779 }
1780 {
1781 var list = ArrayListUnmanaged(u8){};
1782 try list.ensureTotalCapacityPrecise(a, 1);
1783 defer list.deinit(a);
1784
1785 (try list.addManyAsArray(a, 4)).* = "abcd".*;
1786 try list.ensureTotalCapacityPrecise(a, 4);
1787
1788 try list.appendSlice(a, "efgh");
1789 try testing.expectEqualSlices(u8, list.items, "abcdefgh");
1790 try list.ensureTotalCapacityPrecise(a, 8);
1791
1792 try list.insertSlice(a, 4, "ijkl");
1793 try testing.expectEqualSlices(u8, list.items, "abcdijklefgh");
1794 }
1795}
1796
16531797test "std.ArrayList/ArrayList.fromOwnedSliceSentinel" {
16541798 const a = testing.allocator;
16551799