mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 08:18:59 +00:00
new exponential SlabAllocator
This commit is contained in:
@@ -24,7 +24,7 @@ const IS_DEBUG = builtin.mode == .Debug;
|
|||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const String = @import("../string.zig").String;
|
const String = @import("../string.zig").String;
|
||||||
|
|
||||||
const SlabAllocator = @import("../slab.zig").SlabAllocator(16);
|
const SlabAllocator = @import("../slab.zig").SlabAllocator;
|
||||||
|
|
||||||
const Page = @import("Page.zig");
|
const Page = @import("Page.zig");
|
||||||
const Node = @import("webapi/Node.zig");
|
const Node = @import("webapi/Node.zig");
|
||||||
@@ -53,7 +53,7 @@ _slab: SlabAllocator,
|
|||||||
pub fn init(page: *Page) Factory {
|
pub fn init(page: *Page) Factory {
|
||||||
return .{
|
return .{
|
||||||
._page = page,
|
._page = page,
|
||||||
._slab = SlabAllocator.init(page.arena),
|
._slab = SlabAllocator.init(page.arena, 128),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
115
src/slab.zig
115
src/slab.zig
@@ -4,21 +4,10 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Alignment = std.mem.Alignment;
|
const Alignment = std.mem.Alignment;
|
||||||
|
|
||||||
pub fn SlabAllocator(comptime slot_count: usize) type {
|
|
||||||
comptime assert(std.math.isPowerOfTwo(slot_count));
|
|
||||||
|
|
||||||
const SlabKey = struct {
|
|
||||||
size: usize,
|
|
||||||
alignment: Alignment,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Slab = struct {
|
const Slab = struct {
|
||||||
const Slab = @This();
|
|
||||||
const chunk_shift = std.math.log2_int(usize, slot_count);
|
|
||||||
const chunk_mask = slot_count - 1;
|
|
||||||
|
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
item_size: usize,
|
item_size: usize,
|
||||||
|
max_slot_count: usize,
|
||||||
|
|
||||||
bitset: std.bit_set.DynamicBitSetUnmanaged,
|
bitset: std.bit_set.DynamicBitSetUnmanaged,
|
||||||
chunks: std.ArrayListUnmanaged([]u8),
|
chunks: std.ArrayListUnmanaged([]u8),
|
||||||
@@ -27,12 +16,14 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
item_size: usize,
|
item_size: usize,
|
||||||
|
max_slot_count: usize,
|
||||||
) !Slab {
|
) !Slab {
|
||||||
return .{
|
return .{
|
||||||
.alignment = alignment,
|
.alignment = alignment,
|
||||||
.item_size = item_size,
|
.item_size = item_size,
|
||||||
.bitset = try .initFull(allocator, 0),
|
.bitset = try .initFull(allocator, 0),
|
||||||
.chunks = .empty,
|
.chunks = .empty,
|
||||||
|
.max_slot_count = max_slot_count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,23 +37,42 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
self.chunks.deinit(allocator);
|
self.chunks.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn toBitsetIndex(chunk_index: usize, slot_index: usize) usize {
|
inline fn calculateChunkSize(self: *Slab, chunk_index: usize) usize {
|
||||||
return chunk_index * slot_count + slot_index;
|
const safe_index: u6 = @intCast(@min(std.math.maxInt(u6), chunk_index));
|
||||||
|
const exponential = @as(usize, 1) << safe_index;
|
||||||
|
return @min(exponential, self.max_slot_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn chunkIndex(bitset_index: usize) usize {
|
inline fn toBitsetIndex(self: *Slab, chunk_index: usize, slot_index: usize) usize {
|
||||||
return bitset_index >> chunk_shift;
|
var offset: usize = 0;
|
||||||
|
for (0..chunk_index) |i| {
|
||||||
|
const chunk_size = self.calculateChunkSize(i);
|
||||||
|
offset += chunk_size;
|
||||||
|
}
|
||||||
|
return offset + slot_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn slotIndex(bitset_index: usize) usize {
|
inline fn toChunkAndSlotIndices(self: *Slab, bitset_index: usize) struct { usize, usize } {
|
||||||
return bitset_index & chunk_mask;
|
var offset: usize = 0;
|
||||||
|
var chunk_index: usize = 0;
|
||||||
|
|
||||||
|
while (chunk_index < self.chunks.items.len) : (chunk_index += 1) {
|
||||||
|
const chunk_size = self.calculateChunkSize(chunk_index);
|
||||||
|
if (bitset_index < offset + chunk_size) {
|
||||||
|
return .{ chunk_index, bitset_index - offset };
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += chunk_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc(self: *Slab, allocator: Allocator) ![]u8 {
|
fn alloc(self: *Slab, allocator: Allocator) ![]u8 {
|
||||||
if (self.bitset.findFirstSet()) |index| {
|
if (self.bitset.findFirstSet()) |index| {
|
||||||
|
const chunk_index, const slot_index = self.toChunkAndSlotIndices(index);
|
||||||
|
|
||||||
// if we have a free slot
|
// if we have a free slot
|
||||||
const chunk_index = chunkIndex(index);
|
|
||||||
const slot_index = slotIndex(index);
|
|
||||||
self.bitset.unset(index);
|
self.bitset.unset(index);
|
||||||
|
|
||||||
const chunk = self.chunks.items[chunk_index];
|
const chunk = self.chunks.items[chunk_index];
|
||||||
@@ -87,13 +97,13 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
|
|
||||||
for (self.chunks.items, 0..) |chunk, i| {
|
for (self.chunks.items, 0..) |chunk, i| {
|
||||||
const chunk_start = @intFromPtr(chunk.ptr);
|
const chunk_start = @intFromPtr(chunk.ptr);
|
||||||
const chunk_end = chunk_start + (slot_count * self.item_size);
|
const chunk_end = chunk_start + chunk.len;
|
||||||
|
|
||||||
if (addr >= chunk_start and addr < chunk_end) {
|
if (addr >= chunk_start and addr < chunk_end) {
|
||||||
const offset = addr - chunk_start;
|
const offset = addr - chunk_start;
|
||||||
const slot_index = offset / self.item_size;
|
const slot_index = offset / self.item_size;
|
||||||
|
|
||||||
const bitset_index = toBitsetIndex(i, slot_index);
|
const bitset_index = self.toBitsetIndex(i, slot_index);
|
||||||
assert(!self.bitset.isSet(bitset_index));
|
assert(!self.bitset.isSet(bitset_index));
|
||||||
|
|
||||||
self.bitset.set(bitset_index);
|
self.bitset.set(bitset_index);
|
||||||
@@ -105,7 +115,8 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn allocateChunk(self: *Slab, allocator: Allocator) !void {
|
fn allocateChunk(self: *Slab, allocator: Allocator) !void {
|
||||||
const chunk_len = self.item_size * slot_count;
|
const next_chunk_size = self.calculateChunkSize(self.chunks.items.len);
|
||||||
|
const chunk_len = self.item_size * next_chunk_size;
|
||||||
|
|
||||||
const chunk_ptr = allocator.rawAlloc(
|
const chunk_ptr = allocator.rawAlloc(
|
||||||
chunk_len,
|
chunk_len,
|
||||||
@@ -116,7 +127,7 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
const chunk = chunk_ptr[0..chunk_len];
|
const chunk = chunk_ptr[0..chunk_len];
|
||||||
try self.chunks.append(allocator, chunk);
|
try self.chunks.append(allocator, chunk);
|
||||||
|
|
||||||
const new_capacity = self.chunks.items.len * slot_count;
|
const new_capacity = self.bitset.bit_length + next_chunk_size;
|
||||||
try self.bitset.resize(allocator, new_capacity, true);
|
try self.bitset.resize(allocator, new_capacity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +148,7 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
const total_slots = self.bitset.bit_length;
|
const total_slots = self.bitset.bit_length;
|
||||||
const free_slots = self.bitset.count();
|
const free_slots = self.bitset.count();
|
||||||
const used_slots = total_slots - free_slots;
|
const used_slots = total_slots - free_slots;
|
||||||
const bytes_allocated = self.chunks.items.len * slot_count * self.item_size;
|
const bytes_allocated = total_slots * self.item_size;
|
||||||
const bytes_in_use = used_slots * self.item_size;
|
const bytes_in_use = used_slots * self.item_size;
|
||||||
|
|
||||||
const utilization_ratio = if (bytes_allocated > 0)
|
const utilization_ratio = if (bytes_allocated > 0)
|
||||||
@@ -160,10 +171,17 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return struct {
|
const SlabKey = struct {
|
||||||
|
size: usize,
|
||||||
|
alignment: Alignment,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SlabAllocator = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
child_allocator: Allocator,
|
child_allocator: Allocator,
|
||||||
|
max_slot_count: usize,
|
||||||
|
|
||||||
slabs: std.ArrayHashMapUnmanaged(SlabKey, Slab, struct {
|
slabs: std.ArrayHashMapUnmanaged(SlabKey, Slab, struct {
|
||||||
const Context = @This();
|
const Context = @This();
|
||||||
|
|
||||||
@@ -179,10 +197,13 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
}
|
}
|
||||||
}, false) = .empty,
|
}, false) = .empty,
|
||||||
|
|
||||||
pub fn init(child_allocator: Allocator) Self {
|
pub fn init(child_allocator: Allocator, max_slot_count: usize) Self {
|
||||||
|
assert(std.math.isPowerOfTwo(max_slot_count));
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.child_allocator = child_allocator,
|
.child_allocator = child_allocator,
|
||||||
.slabs = .empty,
|
.slabs = .empty,
|
||||||
|
.max_slot_count = max_slot_count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,6 +386,7 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
self.child_allocator,
|
self.child_allocator,
|
||||||
alignment,
|
alignment,
|
||||||
len,
|
len,
|
||||||
|
self.max_slot_count,
|
||||||
) catch return null;
|
) catch return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,14 +406,13 @@ pub fn SlabAllocator(comptime slot_count: usize) type {
|
|||||||
list.free(ptr);
|
list.free(ptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
const TestSlabAllocator = SlabAllocator(32);
|
const TestSlabAllocator = SlabAllocator;
|
||||||
|
|
||||||
test "slab allocator - basic allocation and free" {
|
test "slab allocator - basic allocation and free" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -409,7 +430,7 @@ test "slab allocator - basic allocation and free" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - multiple allocations" {
|
test "slab allocator - multiple allocations" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -432,7 +453,7 @@ test "slab allocator - multiple allocations" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - no coalescing (different size classes)" {
|
test "slab allocator - no coalescing (different size classes)" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -459,7 +480,7 @@ test "slab allocator - no coalescing (different size classes)" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - reuse freed memory" {
|
test "slab allocator - reuse freed memory" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -477,7 +498,7 @@ test "slab allocator - reuse freed memory" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - multiple size classes" {
|
test "slab allocator - multiple size classes" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -501,7 +522,7 @@ test "slab allocator - multiple size classes" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - various sizes" {
|
test "slab allocator - various sizes" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -518,7 +539,7 @@ test "slab allocator - various sizes" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - exact sizes (no rounding)" {
|
test "slab allocator - exact sizes (no rounding)" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -539,7 +560,7 @@ test "slab allocator - exact sizes (no rounding)" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - chunk allocation" {
|
test "slab allocator - chunk allocation" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -561,7 +582,7 @@ test "slab allocator - chunk allocation" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - reset with retain_capacity" {
|
test "slab allocator - reset with retain_capacity" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -588,7 +609,7 @@ test "slab allocator - reset with retain_capacity" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - reset with clear" {
|
test "slab allocator - reset with clear" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -610,7 +631,7 @@ test "slab allocator - reset with clear" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - stress test" {
|
test "slab allocator - stress test" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -647,7 +668,7 @@ test "slab allocator - stress test" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - alignment" {
|
test "slab allocator - alignment" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -662,7 +683,7 @@ test "slab allocator - alignment" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - no resize support" {
|
test "slab allocator - no resize support" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -678,7 +699,7 @@ test "slab allocator - no resize support" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - fragmentation pattern" {
|
test "slab allocator - fragmentation pattern" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -730,7 +751,7 @@ test "slab allocator - fragmentation pattern" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - many small allocations" {
|
test "slab allocator - many small allocations" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -752,11 +773,11 @@ test "slab allocator - many small allocations" {
|
|||||||
|
|
||||||
// Should have created multiple chunks
|
// Should have created multiple chunks
|
||||||
const slab = slab_alloc.slabs.getPtr(.{ .size = 24, .alignment = Alignment.@"1" }).?;
|
const slab = slab_alloc.slabs.getPtr(.{ .size = 24, .alignment = Alignment.@"1" }).?;
|
||||||
try testing.expect(slab.chunks.items.len > 10);
|
try testing.expect(slab.chunks.items.len > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - zero waste for exact sizes" {
|
test "slab allocator - zero waste for exact sizes" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
@@ -776,7 +797,7 @@ test "slab allocator - zero waste for exact sizes" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "slab allocator - different size classes don't interfere" {
|
test "slab allocator - different size classes don't interfere" {
|
||||||
var slab_alloc = TestSlabAllocator.init(testing.allocator);
|
var slab_alloc = TestSlabAllocator.init(testing.allocator, 16);
|
||||||
defer slab_alloc.deinit();
|
defer slab_alloc.deinit();
|
||||||
|
|
||||||
const allocator = slab_alloc.allocator();
|
const allocator = slab_alloc.allocator();
|
||||||
|
|||||||
Reference in New Issue
Block a user