authorgravatar for xq@random-projects.netFelix "xq" Queißner <xq@random-projects.net> 2022-08-23 11:12:24+02:00
committergravatar for xq@random-projects.netFelix "xq" Queißner <xq@random-projects.net> 2022-12-15 09:27:23+01:00
loge28f4a1d859bbfe2f08074159a05e03d30d2cf81
tree2d230ec809b382b19cebda530328067a83b510e1
parent83e0e23f8aa2d90495bbf9c3649aa68f7c55c926

Implements std.heap.MemoryPool and friends.


2 files changed, 191 insertions(+), 0 deletions(-)

lib/std/heap.zig+8
...@@ -20,9 +20,16 @@ pub const WasmAllocator = @import("heap/WasmAllocator.zig");...@@ -20,9 +20,16 @@ pub const WasmAllocator = @import("heap/WasmAllocator.zig");
20pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");20pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
21pub const PageAllocator = @import("heap/PageAllocator.zig");21pub const PageAllocator = @import("heap/PageAllocator.zig");
2222
23const memory_pool = @import("heap/memory_pool.zig");
24pub const MemoryPool = memory_pool.MemoryPool;
25pub const MemoryPoolAligned = memory_pool.MemoryPoolAligned;
26pub const MemoryPoolExtra = memory_pool.MemoryPoolExtra;
27pub const MemoryPoolOptions = memory_pool.Options;
28
23/// TODO Utilize this on Windows.29/// TODO Utilize this on Windows.
24pub var next_mmap_addr_hint: ?[*]align(mem.page_size) u8 = null;30pub var next_mmap_addr_hint: ?[*]align(mem.page_size) u8 = null;
2531
32
26const CAllocator = struct {33const CAllocator = struct {
27 comptime {34 comptime {
28 if (!builtin.link_libc) {35 if (!builtin.link_libc) {
...@@ -847,6 +854,7 @@ test {...@@ -847,6 +854,7 @@ test {
847 _ = LoggingAllocator;854 _ = LoggingAllocator;
848 _ = LogToWriterAllocator;855 _ = LogToWriterAllocator;
849 _ = ScopedLoggingAllocator;856 _ = ScopedLoggingAllocator;
857 _ = @import("heap/memory_pool.zig");
850 _ = ArenaAllocator;858 _ = ArenaAllocator;
851 _ = GeneralPurposeAllocator;859 _ = GeneralPurposeAllocator;
852 if (comptime builtin.target.isWasm()) {860 if (comptime builtin.target.isWasm()) {
lib/std/heap/memory_pool.zig created+183
...@@ -0,0 +1,183 @@
1const std = @import("../std.zig");
2
3const debug_mode = @import("builtin").mode == .Debug;
4
5pub const MemoryPoolError = error{OutOfMemory};
6
7/// A memory pool that can allocate objects of a single type very quickly.
8/// Use this instead of a general purpose allocator when you need to allocate a lot of objects
9/// of the same type, as a memory pool outperforms general purpose allocators.
10pub fn MemoryPool(comptime Item: type) type {
11 return MemoryPoolAligned(Item, @alignOf(Item));
12}
13
14/// A memory pool that can allocate objects of a single type very quickly.
15/// Use this instead of a general purpose allocator when you need to allocate a lot of objects
16/// of the same type, as a memory pool outperforms general purpose allocators.
17pub fn MemoryPoolAligned(comptime Item: type, comptime alignment: u29) type {
18 if (@alignOf(Item) == alignment) {
19 return MemoryPoolExtra(Item, .{});
20 } else {
21 return MemoryPoolExtra(Item, .{ .alignment = alignment });
22 }
23}
24
25pub const Options = struct {
26 /// The alignment of the memory pool items. Use `null` for natural alignment.
27 alignment: ?u29 = null,
28
29 /// If `true`, the memory pool can allocate additional items after a initial setup.
30 /// If `false`, the memory pool will not allocate further after a call to `initPreheated`.
31 growable: bool = true,
32};
33
34/// A memory pool that can allocate objects of a single type very quickly.
35/// Use this instead of a general purpose allocator when you need to allocate a lot of objects
36/// of the same type, as a memory pool outperforms general purpose allocators.
37pub fn MemoryPoolExtra(comptime Item: type, comptime pool_options: Options) type {
38 return struct {
39 const Pool = @This();
40
41 /// Size of the memory pool items. This is not necessarily
42 /// the same as `@sizeOf(Item)` as the pool also uses the items for internal
43 /// means.
44 pub const item_size = std.math.max(@sizeOf(Node), @sizeOf(Item));
45
46 /// Alignment of the memory pool items. This is not necessarily
47 /// the same as `@alignOf(Item)` as the pool also uses the items for internal
48 /// means.
49 pub const item_alignment = std.math.max(@alignOf(Node), pool_options.alignment orelse 0);
50
51 const Node = struct {
52 next: ?*@This(),
53 };
54 const NodePtr = *align(item_alignment) Node;
55 const ItemPtr = *align(item_alignment) Item;
56
57 arena: std.heap.ArenaAllocator,
58 free_list: ?NodePtr = null,
59
60 /// Creates a new memory pool.
61 pub fn init(allocator: std.mem.Allocator) Pool {
62 return .{ .arena = std.heap.ArenaAllocator.init(allocator) };
63 }
64
65 /// Creates a new memory pool and pre-allocates `initial_size` items.
66 /// This allows the up to `initial_size` active allocations before a
67 /// `OutOfMemory` error happens when calling `create()`.
68 pub fn initPreheated(allocator: std.mem.Allocator, initial_size: usize) MemoryPoolError!Pool {
69 var pool = init(allocator);
70 errdefer pool.deinit();
71
72 var i: usize = 0;
73 while (i < initial_size) : (i += 1) {
74 const raw_mem = try pool.allocNew();
75 const free_node = @ptrCast(NodePtr, raw_mem);
76 free_node.* = Node{
77 .next = pool.free_list,
78 };
79 pool.free_list = free_node;
80 }
81
82 return pool;
83 }
84
85 /// Destroys the memory pool and frees all allocated memory.
86 pub fn deinit(pool: *Pool) void {
87 pool.arena.deinit();
88 pool.* = undefined;
89 }
90
91 /// Resets the memory pool and destroys all allocated items.
92 /// This can be used to batch-destroy all objects without invalidating the memory pool.
93 pub fn reset(pool: *Pool) void {
94 // TODO: Potentially store all allocated objects in a list as well, allowing to
95 // just move them into the free list instead of actually releasing the memory.
96 const allocator = pool.arena.child_allocator;
97
98 // TODO: Replace with "pool.arena.reset()" when implemented.
99 pool.arena.deinit();
100 pool.arena = std.heap.ArenaAllocator.init(allocator);
101
102 pool.free_list = null;
103 }
104
105 /// Creates a new item and adds it to the memory pool.
106 pub fn create(pool: *Pool) !ItemPtr {
107 const node = if (pool.free_list) |item| blk: {
108 pool.free_list = item.next;
109 break :blk item;
110 } else if (pool_options.growable)
111 @ptrCast(NodePtr, try pool.allocNew())
112 else
113 return error.OutOfMemory;
114
115 const ptr = @ptrCast(ItemPtr, node);
116 ptr.* = undefined;
117 return ptr;
118 }
119
120 /// Destroys a previously created item.
121 /// Only pass items to `ptr` that were previously created with `create()` of the same memory pool!
122 pub fn destroy(pool: *Pool, ptr: ItemPtr) void {
123 ptr.* = undefined;
124
125 const node = @ptrCast(NodePtr, ptr);
126 node.* = Node{
127 .next = pool.free_list,
128 };
129 pool.free_list = node;
130 }
131
132 fn allocNew(pool: *Pool) MemoryPoolError!*align(item_alignment) [item_size]u8 {
133 const mem = try pool.arena.allocator().alignedAlloc(u8, item_alignment, item_size);
134 return mem[0..item_size]; // coerce slice to array pointer
135 }
136 };
137}
138
139test "memory pool: basic" {
140 var pool = MemoryPool(u32).init(std.testing.allocator);
141 defer pool.deinit();
142
143 const p1 = try pool.create();
144 const p2 = try pool.create();
145 const p3 = try pool.create();
146
147 // Assert uniqueness
148 try std.testing.expect(p1 != p2);
149 try std.testing.expect(p1 != p3);
150 try std.testing.expect(p2 != p3);
151
152 pool.destroy(p2);
153 const p4 = try pool.create();
154
155 // Assert memory resuse
156 try std.testing.expect(p2 == p4);
157}
158
159test "memory pool: preheating (success)" {
160 var pool = try MemoryPool(u32).initPreheated(std.testing.allocator, 4);
161 defer pool.deinit();
162
163 _ = try pool.create();
164 _ = try pool.create();
165 _ = try pool.create();
166}
167
168test "memory pool: preheating (failure)" {
169 var failer = std.testing.FailingAllocator.init(std.testing.allocator, 0);
170 try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer.allocator(), 5));
171}
172
173test "memory pool: growable" {
174 var pool = try MemoryPoolExtra(u32, .{ .growable = false }).initPreheated(std.testing.allocator, 4);
175 defer pool.deinit();
176
177 _ = try pool.create();
178 _ = try pool.create();
179 _ = try pool.create();
180 _ = try pool.create();
181
182 try std.testing.expectError(error.OutOfMemory, pool.create());
183}