aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/std/heap.zig7
-rw-r--r--lib/std/heap/memory_pool.zig181
2 files changed, 188 insertions, 0 deletions
diff --git a/lib/std/heap.zig b/lib/std/heap.zig
index 9c0abc62a6..d8e88c4933 100644
--- a/lib/std/heap.zig
+++ b/lib/std/heap.zig
@@ -20,6 +20,12 @@ pub const WasmAllocator = @import("heap/WasmAllocator.zig");
pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
pub const PageAllocator = @import("heap/PageAllocator.zig");
+const memory_pool = @import("heap/memory_pool.zig");
+pub const MemoryPool = memory_pool.MemoryPool;
+pub const MemoryPoolAligned = memory_pool.MemoryPoolAligned;
+pub const MemoryPoolExtra = memory_pool.MemoryPoolExtra;
+pub const MemoryPoolOptions = memory_pool.Options;
+
/// TODO Utilize this on Windows.
pub var next_mmap_addr_hint: ?[*]align(mem.page_size) u8 = null;
@@ -851,6 +857,7 @@ test {
_ = LoggingAllocator;
_ = LogToWriterAllocator;
_ = ScopedLoggingAllocator;
+ _ = @import("heap/memory_pool.zig");
_ = ArenaAllocator;
_ = GeneralPurposeAllocator;
if (comptime builtin.target.isWasm()) {
diff --git a/lib/std/heap/memory_pool.zig b/lib/std/heap/memory_pool.zig
new file mode 100644
index 0000000000..6ae90a3894
--- /dev/null
+++ b/lib/std/heap/memory_pool.zig
@@ -0,0 +1,181 @@
+const std = @import("../std.zig");
+
+const debug_mode = @import("builtin").mode == .Debug;
+
+pub const MemoryPoolError = error{OutOfMemory};
+
+/// A memory pool that can allocate objects of a single type very quickly.
+/// Use this when you need to allocate a lot of objects of the same type,
+/// because It outperforms general purpose allocators.
+pub fn MemoryPool(comptime Item: type) type {
+ return MemoryPoolAligned(Item, @alignOf(Item));
+}
+
+/// A memory pool that can allocate objects of a single type very quickly.
+/// Use this when you need to allocate a lot of objects of the same type,
+/// because It outperforms general purpose allocators.
+pub fn MemoryPoolAligned(comptime Item: type, comptime alignment: u29) type {
+ if (@alignOf(Item) == alignment) {
+ return MemoryPoolExtra(Item, .{});
+ } else {
+ return MemoryPoolExtra(Item, .{ .alignment = alignment });
+ }
+}
+
+pub const Options = struct {
+ /// The alignment of the memory pool items. Use `null` for natural alignment.
+ alignment: ?u29 = null,
+
+ /// If `true`, the memory pool can allocate additional items after a initial setup.
+ /// If `false`, the memory pool will not allocate further after a call to `initPreheated`.
+ growable: bool = true,
+};
+
+/// A memory pool that can allocate objects of a single type very quickly.
+/// Use this when you need to allocate a lot of objects of the same type,
+/// because It outperforms general purpose allocators.
+pub fn MemoryPoolExtra(comptime Item: type, comptime pool_options: Options) type {
+ return struct {
+ const Pool = @This();
+
+ /// Size of the memory pool items. This is not necessarily the same
+ /// as `@sizeOf(Item)` as the pool also uses the items for internal means.
+ pub const item_size = std.math.max(@sizeOf(Node), @sizeOf(Item));
+
+ /// Alignment of the memory pool items. This is not necessarily the same
+ /// as `@alignOf(Item)` as the pool also uses the items for internal means.
+ pub const item_alignment = std.math.max(@alignOf(Node), pool_options.alignment orelse 0);
+
+ const Node = struct {
+ next: ?*@This(),
+ };
+ const NodePtr = *align(item_alignment) Node;
+ const ItemPtr = *align(item_alignment) Item;
+
+ arena: std.heap.ArenaAllocator,
+ free_list: ?NodePtr = null,
+
+ /// Creates a new memory pool.
+ pub fn init(allocator: std.mem.Allocator) Pool {
+ return .{ .arena = std.heap.ArenaAllocator.init(allocator) };
+ }
+
+ /// Creates a new memory pool and pre-allocates `initial_size` items.
+ /// This allows the up to `initial_size` active allocations before a
+ /// `OutOfMemory` error happens when calling `create()`.
+ pub fn initPreheated(allocator: std.mem.Allocator, initial_size: usize) MemoryPoolError!Pool {
+ var pool = init(allocator);
+ errdefer pool.deinit();
+
+ var i: usize = 0;
+ while (i < initial_size) : (i += 1) {
+ const raw_mem = try pool.allocNew();
+ const free_node = @ptrCast(NodePtr, raw_mem);
+ free_node.* = Node{
+ .next = pool.free_list,
+ };
+ pool.free_list = free_node;
+ }
+
+ return pool;
+ }
+
+ /// Destroys the memory pool and frees all allocated memory.
+ pub fn deinit(pool: *Pool) void {
+ pool.arena.deinit();
+ pool.* = undefined;
+ }
+
+ /// Resets the memory pool and destroys all allocated items.
+ /// This can be used to batch-destroy all objects without invalidating the memory pool.
+ pub fn reset(pool: *Pool) void {
+ // TODO: Potentially store all allocated objects in a list as well, allowing to
+ // just move them into the free list instead of actually releasing the memory.
+ const allocator = pool.arena.child_allocator;
+
+ // TODO: Replace with "pool.arena.reset()" when implemented.
+ pool.arena.deinit();
+ pool.arena = std.heap.ArenaAllocator.init(allocator);
+
+ pool.free_list = null;
+ }
+
+ /// Creates a new item and adds it to the memory pool.
+ pub fn create(pool: *Pool) !ItemPtr {
+ const node = if (pool.free_list) |item| blk: {
+ pool.free_list = item.next;
+ break :blk item;
+ } else if (pool_options.growable)
+ @ptrCast(NodePtr, try pool.allocNew())
+ else
+ return error.OutOfMemory;
+
+ const ptr = @ptrCast(ItemPtr, node);
+ ptr.* = undefined;
+ return ptr;
+ }
+
+ /// Destroys a previously created item.
+ /// Only pass items to `ptr` that were previously created with `create()` of the same memory pool!
+ pub fn destroy(pool: *Pool, ptr: ItemPtr) void {
+ ptr.* = undefined;
+
+ const node = @ptrCast(NodePtr, ptr);
+ node.* = Node{
+ .next = pool.free_list,
+ };
+ pool.free_list = node;
+ }
+
+ fn allocNew(pool: *Pool) MemoryPoolError!*align(item_alignment) [item_size]u8 {
+ const mem = try pool.arena.allocator().alignedAlloc(u8, item_alignment, item_size);
+ return mem[0..item_size]; // coerce slice to array pointer
+ }
+ };
+}
+
+test "memory pool: basic" {
+ var pool = MemoryPool(u32).init(std.testing.allocator);
+ defer pool.deinit();
+
+ const p1 = try pool.create();
+ const p2 = try pool.create();
+ const p3 = try pool.create();
+
+ // Assert uniqueness
+ try std.testing.expect(p1 != p2);
+ try std.testing.expect(p1 != p3);
+ try std.testing.expect(p2 != p3);
+
+ pool.destroy(p2);
+ const p4 = try pool.create();
+
+ // Assert memory resuse
+ try std.testing.expect(p2 == p4);
+}
+
+test "memory pool: preheating (success)" {
+ var pool = try MemoryPool(u32).initPreheated(std.testing.allocator, 4);
+ defer pool.deinit();
+
+ _ = try pool.create();
+ _ = try pool.create();
+ _ = try pool.create();
+}
+
+test "memory pool: preheating (failure)" {
+ var failer = std.testing.FailingAllocator.init(std.testing.allocator, 0);
+ try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer.allocator(), 5));
+}
+
+test "memory pool: growable" {
+ var pool = try MemoryPoolExtra(u32, .{ .growable = false }).initPreheated(std.testing.allocator, 4);
+ defer pool.deinit();
+
+ _ = try pool.create();
+ _ = try pool.create();
+ _ = try pool.create();
+ _ = try pool.create();
+
+ try std.testing.expectError(error.OutOfMemory, pool.create());
+}