authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-11 18:53:13-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-13 00:19:03-08:00
logd12123a88cf56428874b359cb63acb8618dfbcfd
treecb4798e72f47d303d5ac527467b021f467c86818
parent5b9b5e45cb710ddaad1a97813d1619755eb35a98

std.ArrayList: initial capacity based on cache line size

also std.MultiArrayList

5 files changed, 47 insertions(+), 32 deletions(-)

lib/std/array_list.zig+19-19
...@@ -181,7 +181,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -181,7 +181,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
181 // a new buffer and doing our own copy. With a realloc() call,181 // a new buffer and doing our own copy. With a realloc() call,
182 // the allocator implementation would pointlessly copy our182 // the allocator implementation would pointlessly copy our
183 // extra capacity.183 // extra capacity.
184 const new_capacity = growCapacity(self.capacity, new_len);184 const new_capacity = ArrayListAlignedUnmanaged(T, alignment).growCapacity(self.capacity, new_len);
185 const old_memory = self.allocatedSlice();185 const old_memory = self.allocatedSlice();
186 if (self.allocator.remap(old_memory, new_capacity)) |new_memory| {186 if (self.allocator.remap(old_memory, new_capacity)) |new_memory| {
187 self.items.ptr = new_memory.ptr;187 self.items.ptr = new_memory.ptr;
...@@ -446,7 +446,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -446,7 +446,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
446446
447 if (self.capacity >= new_capacity) return;447 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);
450 return self.ensureTotalCapacityPrecise(better_capacity);450 return self.ensureTotalCapacityPrecise(better_capacity);
451 }451 }
452452
...@@ -1062,14 +1062,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -1062,14 +1062,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
1062 self.capacity = 0;1062 self.capacity = 0;
1063 }1063 }
10641064
1065 /// If the current capacity is less than `new_capacity`, this function will1065 /// Modify the array so that it can hold at least `new_capacity` items.
1066 /// 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.
1067 /// Invalidates element pointers if additional memory is needed.1067 /// 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 {
1069 if (self.capacity >= new_capacity) return;1069 if (self.capacity >= new_capacity) return;
10701070 return self.ensureTotalCapacityPrecise(gpa, growCapacity(self.capacity, new_capacity));
1071 const better_capacity = growCapacity(self.capacity, new_capacity);
1072 return self.ensureTotalCapacityPrecise(allocator, better_capacity);
1073 }1071 }
10741072
1075 /// If the current capacity is less than `new_capacity`, this function will1073 /// 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...@@ -1218,18 +1216,20 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
1218 if (self.items.len == 0) return null;1216 if (self.items.len == 0) return null;
1219 return self.getLast();1217 return self.getLast();
1220 }1218 }
1221 };
1222}
12231219
1224/// Called when memory growth is necessary. Returns a capacity larger than1220 const init_capacity = @as(comptime_int, @max(1, std.atomic.cache_line / @sizeOf(T)));
1225/// minimum that grows super-linearly.1221
1226fn growCapacity(current: usize, minimum: usize) usize {1222 /// Called when memory growth is necessary. Returns a capacity larger than
1227 var new = current;1223 /// minimum that grows super-linearly.
1228 while (true) {1224 fn growCapacity(current: usize, minimum: usize) usize {
1229 new +|= new / 2 + 8;1225 var new = current;
1230 if (new >= minimum)1226 while (true) {
1231 return new;1227 new +|= new / 2 + init_capacity;
1232 }1228 if (new >= minimum)
1229 return new;
1230 }
1231 }
1232 };
1233}1233}
12341234
1235/// Integer addition returning `error.OutOfMemory` on overflow.1235/// Integer addition returning `error.OutOfMemory` on overflow.
lib/std/json/scanner_test.zig+7-2
...@@ -435,8 +435,13 @@ fn testEnsureStackCapacity(do_ensure: bool) !void {...@@ -435,8 +435,13 @@ fn testEnsureStackCapacity(do_ensure: bool) !void {
435 var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 });435 var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 });
436 const failing_allocator = fail_alloc.allocator();436 const failing_allocator = fail_alloc.allocator();
437437
438 const nestings = 999; // intentionally not a power of 2.438 const nestings = 2049; // intentionally not a power of 2.
439 var scanner = JsonScanner.initCompleteInput(failing_allocator, "[" ** nestings ++ "]" ** nestings);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);
440 defer scanner.deinit();445 defer scanner.deinit();
441446
442 if (do_ensure) {447 if (do_ensure) {
lib/std/json/static_test.zig+1-1
...@@ -916,7 +916,7 @@ test "parse at comptime" {...@@ -916,7 +916,7 @@ test "parse at comptime" {
916 uptime: u64,916 uptime: u64,
917 };917 };
918 const config = comptime x: {918 const config = comptime x: {
919 var buf: [32]u8 = undefined;919 var buf: [256]u8 = undefined;
920 var fba = std.heap.FixedBufferAllocator.init(&buf);920 var fba = std.heap.FixedBufferAllocator.init(&buf);
921 const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{});921 const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{});
922 // Assert no error can occur since we are922 // Assert no error can occur since we are
lib/std/multi_array_list.zig+19-9
...@@ -405,17 +405,27 @@ pub fn MultiArrayList(comptime T: type) type {...@@ -405,17 +405,27 @@ pub fn MultiArrayList(comptime T: type) type {
405405
406 /// Modify the array so that it can hold at least `new_capacity` items.406 /// Modify the array so that it can hold at least `new_capacity` items.
407 /// Implements super-linear growth to achieve amortized O(1) append operations.407 /// Implements super-linear growth to achieve amortized O(1) append operations.
408 /// Invalidates pointers if additional memory is needed.408 /// Invalidates element pointers if additional memory is needed.
409 pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) !void {409 pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
410 var better_capacity = self.capacity;410 if (self.capacity >= new_capacity) return;
411 if (better_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;
413 while (true) {424 while (true) {
414 better_capacity += better_capacity / 2 + 8;425 new +|= new / 2 + init_capacity;
415 if (better_capacity >= new_capacity) break;426 if (new >= minimum)
427 return new;
416 }428 }
417
418 return self.setCapacity(gpa, better_capacity);
419 }429 }
420430
421 /// Modify the array so that it can hold at least `additional_count` **more** items.431 /// Modify the array so that it can hold at least `additional_count` **more** items.
...@@ -838,7 +848,7 @@ test "union" {...@@ -838,7 +848,7 @@ test "union" {
838848
839 try testing.expectEqual(@as(usize, 0), list.items(.tags).len);849 try testing.expectEqual(@as(usize, 0), list.items(.tags).len);
840850
841 try list.ensureTotalCapacity(ally, 2);851 try list.ensureTotalCapacity(ally, 3);
842852
843 list.appendAssumeCapacity(.{ .a = 1 });853 list.appendAssumeCapacity(.{ .a = 1 });
844 list.appendAssumeCapacity(.{ .b = "zigzag" });854 list.appendAssumeCapacity(.{ .b = "zigzag" });
lib/std/zig/string_literal.zig+1-1
...@@ -377,7 +377,7 @@ test parseAlloc {...@@ -377,7 +377,7 @@ test parseAlloc {
377 const expectError = std.testing.expectError;377 const expectError = std.testing.expectError;
378 const eql = std.mem.eql;378 const eql = std.mem.eql;
379379
380 var fixed_buf_mem: [64]u8 = undefined;380 var fixed_buf_mem: [512]u8 = undefined;
381 var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buf_mem);381 var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buf_mem);
382 const alloc = fixed_buf_alloc.allocator();382 const alloc = fixed_buf_alloc.allocator();
383383