Skip to content

Commit d12123a

Browse files
committed
std.ArrayList: initial capacity based on cache line size
also std.MultiArrayList
1 parent 5b9b5e4 commit d12123a

File tree

5 files changed

+47
-32
lines changed

5 files changed

+47
-32
lines changed

lib/std/array_list.zig

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
181181
// a new buffer and doing our own copy. With a realloc() call,
182182
// the allocator implementation would pointlessly copy our
183183
// extra capacity.
184-
const new_capacity = growCapacity(self.capacity, new_len);
184+
const new_capacity = ArrayListAlignedUnmanaged(T, alignment).growCapacity(self.capacity, new_len);
185185
const old_memory = self.allocatedSlice();
186186
if (self.allocator.remap(old_memory, new_capacity)) |new_memory| {
187187
self.items.ptr = new_memory.ptr;
@@ -446,7 +446,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
446446

447447
if (self.capacity >= new_capacity) return;
448448

449-
const better_capacity = growCapacity(self.capacity, new_capacity);
449+
const better_capacity = ArrayListAlignedUnmanaged(T, alignment).growCapacity(self.capacity, new_capacity);
450450
return self.ensureTotalCapacityPrecise(better_capacity);
451451
}
452452

@@ -1062,14 +1062,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
10621062
self.capacity = 0;
10631063
}
10641064

1065-
/// If the current capacity is less than `new_capacity`, this function will
1066-
/// modify the array so that it can hold at least `new_capacity` items.
1065+
/// Modify the array so that it can hold at least `new_capacity` items.
1066+
/// Implements super-linear growth to achieve amortized O(1) append operations.
10671067
/// Invalidates element pointers if additional memory is needed.
1068-
pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
1068+
pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
10691069
if (self.capacity >= new_capacity) return;
1070-
1071-
const better_capacity = growCapacity(self.capacity, new_capacity);
1072-
return self.ensureTotalCapacityPrecise(allocator, better_capacity);
1070+
return self.ensureTotalCapacityPrecise(gpa, growCapacity(self.capacity, new_capacity));
10731071
}
10741072

10751073
/// If the current capacity is less than `new_capacity`, this function will
@@ -1218,18 +1216,20 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
12181216
if (self.items.len == 0) return null;
12191217
return self.getLast();
12201218
}
1221-
};
1222-
}
12231219

1224-
/// Called when memory growth is necessary. Returns a capacity larger than
1225-
/// minimum that grows super-linearly.
1226-
fn growCapacity(current: usize, minimum: usize) usize {
1227-
var new = current;
1228-
while (true) {
1229-
new +|= new / 2 + 8;
1230-
if (new >= minimum)
1231-
return new;
1232-
}
1220+
const init_capacity = @as(comptime_int, @max(1, std.atomic.cache_line / @sizeOf(T)));
1221+
1222+
/// Called when memory growth is necessary. Returns a capacity larger than
1223+
/// minimum that grows super-linearly.
1224+
fn growCapacity(current: usize, minimum: usize) usize {
1225+
var new = current;
1226+
while (true) {
1227+
new +|= new / 2 + init_capacity;
1228+
if (new >= minimum)
1229+
return new;
1230+
}
1231+
}
1232+
};
12331233
}
12341234

12351235
/// Integer addition returning `error.OutOfMemory` on overflow.

lib/std/json/scanner_test.zig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,13 @@ fn testEnsureStackCapacity(do_ensure: bool) !void {
435435
var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 });
436436
const failing_allocator = fail_alloc.allocator();
437437

438-
const nestings = 999; // intentionally not a power of 2.
439-
var scanner = JsonScanner.initCompleteInput(failing_allocator, "[" ** nestings ++ "]" ** nestings);
438+
const nestings = 2049; // intentionally not a power of 2.
439+
var input_string: std.ArrayListUnmanaged(u8) = .empty;
440+
try input_string.appendNTimes(std.testing.allocator, '[', nestings);
441+
try input_string.appendNTimes(std.testing.allocator, ']', nestings);
442+
defer input_string.deinit(std.testing.allocator);
443+
444+
var scanner = JsonScanner.initCompleteInput(failing_allocator, input_string.items);
440445
defer scanner.deinit();
441446

442447
if (do_ensure) {

lib/std/json/static_test.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ test "parse at comptime" {
916916
uptime: u64,
917917
};
918918
const config = comptime x: {
919-
var buf: [32]u8 = undefined;
919+
var buf: [256]u8 = undefined;
920920
var fba = std.heap.FixedBufferAllocator.init(&buf);
921921
const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{});
922922
// Assert no error can occur since we are

lib/std/multi_array_list.zig

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -405,17 +405,27 @@ pub fn MultiArrayList(comptime T: type) type {
405405

406406
/// Modify the array so that it can hold at least `new_capacity` items.
407407
/// Implements super-linear growth to achieve amortized O(1) append operations.
408-
/// Invalidates pointers if additional memory is needed.
409-
pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) !void {
410-
var better_capacity = self.capacity;
411-
if (better_capacity >= new_capacity) return;
408+
/// Invalidates element pointers if additional memory is needed.
409+
pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
410+
if (self.capacity >= new_capacity) return;
411+
return self.setCapacity(gpa, growCapacity(self.capacity, new_capacity));
412+
}
413+
414+
const init_capacity = init: {
415+
var max = 1;
416+
for (fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type)));
417+
break :init @as(comptime_int, @max(1, std.atomic.cache_line / max));
418+
};
412419

420+
/// Called when memory growth is necessary. Returns a capacity larger than
421+
/// minimum that grows super-linearly.
422+
fn growCapacity(current: usize, minimum: usize) usize {
423+
var new = current;
413424
while (true) {
414-
better_capacity += better_capacity / 2 + 8;
415-
if (better_capacity >= new_capacity) break;
425+
new +|= new / 2 + init_capacity;
426+
if (new >= minimum)
427+
return new;
416428
}
417-
418-
return self.setCapacity(gpa, better_capacity);
419429
}
420430

421431
/// Modify the array so that it can hold at least `additional_count` **more** items.
@@ -838,7 +848,7 @@ test "union" {
838848

839849
try testing.expectEqual(@as(usize, 0), list.items(.tags).len);
840850

841-
try list.ensureTotalCapacity(ally, 2);
851+
try list.ensureTotalCapacity(ally, 3);
842852

843853
list.appendAssumeCapacity(.{ .a = 1 });
844854
list.appendAssumeCapacity(.{ .b = "zigzag" });

lib/std/zig/string_literal.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ test parseAlloc {
377377
const expectError = std.testing.expectError;
378378
const eql = std.mem.eql;
379379

380-
var fixed_buf_mem: [64]u8 = undefined;
380+
var fixed_buf_mem: [512]u8 = undefined;
381381
var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buf_mem);
382382
const alloc = fixed_buf_alloc.allocator();
383383

0 commit comments

Comments
 (0)