Skip to content

Commit

Permalink
std.ArrayList: initial capacity based on cache line size
Browse files Browse the repository at this point in the history
also std.MultiArrayList
  • Loading branch information
andrewrk committed Feb 13, 2025
1 parent 5b9b5e4 commit a7c206b
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 32 deletions.
38 changes: 19 additions & 19 deletions lib/std/array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 7 additions & 2 deletions lib/std/json/scanner_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion lib/std/json/static_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 19 additions & 9 deletions lib/std/multi_array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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" });
Expand Down
2 changes: 1 addition & 1 deletion lib/std/zig/string_literal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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: [256]u8 = undefined;
var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buf_mem);
const alloc = fixed_buf_alloc.allocator();

Expand Down

0 comments on commit a7c206b

Please sign in to comment.