From 40e5f217e8e3116af43be8820963c4121ade9e86 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 11 Feb 2025 18:53:13 -0800 Subject: [PATCH] std.ArrayList: initial capacity based on cache line size also std.MultiArrayList --- lib/std/array_list.zig | 38 +++++++++++++++++----------------- lib/std/json/scanner_test.zig | 9 ++++++-- lib/std/json/static_test.zig | 2 +- lib/std/multi_array_list.zig | 28 +++++++++++++++++-------- lib/std/zig/string_literal.zig | 2 +- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 11419f975a45..1b2bbcb9194f 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -181,7 +181,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { // a new buffer and doing our own copy. With a realloc() call, // the allocator implementation would pointlessly copy our // extra capacity. - const new_capacity = growCapacity(self.capacity, new_len); + const new_capacity = ArrayListAlignedUnmanaged(T, alignment).growCapacity(self.capacity, new_len); const old_memory = self.allocatedSlice(); if (self.allocator.remap(old_memory, new_capacity)) |new_memory| { self.items.ptr = new_memory.ptr; @@ -446,7 +446,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { if (self.capacity >= new_capacity) return; - const better_capacity = growCapacity(self.capacity, new_capacity); + const better_capacity = ArrayListAlignedUnmanaged(T, alignment).growCapacity(self.capacity, new_capacity); return self.ensureTotalCapacityPrecise(better_capacity); } @@ -1062,14 +1062,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ self.capacity = 0; } - /// If the current capacity is less than `new_capacity`, this function will - /// modify the array so that it can hold at least `new_capacity` items. + /// Modify the array so that it can hold at least `new_capacity` items. + /// Implements super-linear growth to achieve amortized O(1) append operations. /// Invalidates element pointers if additional memory is needed. - pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void { + pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void { if (self.capacity >= new_capacity) return; - - const better_capacity = growCapacity(self.capacity, new_capacity); - return self.ensureTotalCapacityPrecise(allocator, better_capacity); + return self.ensureTotalCapacityPrecise(gpa, growCapacity(self.capacity, new_capacity)); } /// 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 if (self.items.len == 0) return null; return self.getLast(); } - }; -} -/// Called when memory growth is necessary. Returns a capacity larger than -/// minimum that grows super-linearly. -fn growCapacity(current: usize, minimum: usize) usize { - var new = current; - while (true) { - new +|= new / 2 + 8; - if (new >= minimum) - return new; - } + const init_capacity = @as(comptime_int, @max(1, std.atomic.cache_line / @sizeOf(T))); + + /// Called when memory growth is necessary. Returns a capacity larger than + /// minimum that grows super-linearly. + fn growCapacity(current: usize, minimum: usize) usize { + var new = current; + while (true) { + new +|= new / 2 + init_capacity; + if (new >= minimum) + return new; + } + } + }; } /// Integer addition returning `error.OutOfMemory` on overflow. diff --git a/lib/std/json/scanner_test.zig b/lib/std/json/scanner_test.zig index aa646f2a2df9..d085cb661a7b 100644 --- a/lib/std/json/scanner_test.zig +++ b/lib/std/json/scanner_test.zig @@ -435,8 +435,13 @@ fn testEnsureStackCapacity(do_ensure: bool) !void { var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 }); const failing_allocator = fail_alloc.allocator(); - const nestings = 999; // intentionally not a power of 2. - var scanner = JsonScanner.initCompleteInput(failing_allocator, "[" ** nestings ++ "]" ** nestings); + const nestings = 2049; // intentionally not a power of 2. + var input_string: std.ArrayListUnmanaged(u8) = .empty; + try input_string.appendNTimes(std.testing.allocator, '[', nestings); + try input_string.appendNTimes(std.testing.allocator, ']', nestings); + defer input_string.deinit(std.testing.allocator); + + var scanner = JsonScanner.initCompleteInput(failing_allocator, input_string.items); defer scanner.deinit(); if (do_ensure) { diff --git a/lib/std/json/static_test.zig b/lib/std/json/static_test.zig index 1c89e05226ed..3375ae057216 100644 --- a/lib/std/json/static_test.zig +++ b/lib/std/json/static_test.zig @@ -916,7 +916,7 @@ test "parse at comptime" { uptime: u64, }; const config = comptime x: { - var buf: [32]u8 = undefined; + var buf: [256]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&buf); const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{}); // Assert no error can occur since we are diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 44eea7d57ef7..7d68322c90a0 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -405,17 +405,27 @@ pub fn MultiArrayList(comptime T: type) type { /// Modify the array so that it can hold at least `new_capacity` items. /// Implements super-linear growth to achieve amortized O(1) append operations. - /// Invalidates pointers if additional memory is needed. - pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) !void { - var better_capacity = self.capacity; - if (better_capacity >= new_capacity) return; + /// Invalidates element pointers if additional memory is needed. + pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void { + if (self.capacity >= new_capacity) return; + return self.setCapacity(gpa, growCapacity(self.capacity, new_capacity)); + } + + const init_capacity = init: { + var max = 1; + for (fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type))); + break :init @as(comptime_int, @max(1, std.atomic.cache_line / max)); + }; + /// Called when memory growth is necessary. Returns a capacity larger than + /// minimum that grows super-linearly. + fn growCapacity(current: usize, minimum: usize) usize { + var new = current; while (true) { - better_capacity += better_capacity / 2 + 8; - if (better_capacity >= new_capacity) break; + new +|= new / 2 + init_capacity; + if (new >= minimum) + return new; } - - return self.setCapacity(gpa, better_capacity); } /// Modify the array so that it can hold at least `additional_count` **more** items. @@ -838,7 +848,7 @@ test "union" { try testing.expectEqual(@as(usize, 0), list.items(.tags).len); - try list.ensureTotalCapacity(ally, 2); + try list.ensureTotalCapacity(ally, 3); list.appendAssumeCapacity(.{ .a = 1 }); list.appendAssumeCapacity(.{ .b = "zigzag" }); diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 2dff70d70b9e..972219abbd24 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -377,7 +377,7 @@ test parseAlloc { const expectError = std.testing.expectError; const eql = std.mem.eql; - var fixed_buf_mem: [64]u8 = undefined; + var fixed_buf_mem: [512]u8 = undefined; var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buf_mem); const alloc = fixed_buf_alloc.allocator();