Skip to content

Commit 101df76

Browse files
authored
Merge pull request #17312 from LucasSantos91/master
Fix inefficiency with ArrayList.insertSlice
2 parents 19a82ff + 9013970 commit 101df76

File tree

1 file changed

+174
-30
lines changed

1 file changed

+174
-30
lines changed

lib/std/array_list.zig

Lines changed: 174 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
1143+
fn 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+
10421152
test "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

1763+
test "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+
16531797
test "std.ArrayList/ArrayList.fromOwnedSliceSentinel" {
16541798
const a = testing.allocator;
16551799

0 commit comments

Comments
 (0)