Hi, first of all, I would like to say that I’ve read the section Choosing an Allocator in the language documentation. I also found the following post, which answers most of my questions:
But a few questions remain.
Let me cite the language documentation first and throw in some questions:
- Are you linking libc? In this case,
std.heap.c_allocatoris likely the right choice, at least for your main allocator.
Why is std.heap.c_allocator a better choice than GPA (assuming libc is linked)?
Side question: Why is the GeneralPurposeAllocator (GPA) shown as DebugAllocator when I click on GeneralPurposeAllocator on this page?
- Need to use the same allocator in multiple threads? Use one of your choice wrapped around
std.heap.ThreadSafeAllocator.
When would (or should) I use the same allocator in multiple threads? Is there a problem to create a GPA for each thread?
- Is your program a command line application which runs from start to end without any fundamental cyclical pattern (such as a video game main loop, or a web server request handler), such that it would make sense to free everything at once at the end? In this case, it is recommended to follow this pattern:
const std = @import("std"); pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const ptr = try allocator.create(i32); std.debug.print("ptr={*}\n", .{ptr}); }When using this kind of allocator, there is no need to free anything manually. Everything gets freed at once with the call to
arena.deinit().
Yeah okay, but… what about the backing allocator? In that example the std.heap.page_allocator is used, which allocates at least a whole page, even if only a tiny bit of memory is requested. Does the ArenaAllocator initialized with the page_allocator then also make one syscall per allocation, or is there some additional managment (other than freeing everything at the end) added on top?
Also, why would I need to free everything at the end anyway if I’m writing a command line application and the process is terminated by the OS in the end?
- […]
Same question regarding choice for a backing allocator applies here.
- Are you writing a test, and you want to make sure
error.OutOfMemoryis handled correctly? In this case, usestd.testing.FailingAllocator.- Are you writing a test? In this case, use
std.testing.allocator.
What does it mean that “OutOfMemory is handled correctly”? That my test should handle it correctly? Or that the testing framework will handle it correctly? When to use which of those two allocators in tests?
- Finally, if none of the above apply, you need a general purpose allocator. Zig’s general purpose allocator is available as a function that takes a comptime struct of configuration options and returns a type. Generally, you will set up one
std.heap.GeneralPurposeAllocatorin your main function, and then pass it or sub-allocators around to various parts of your application.
Okay I get that. However, what are the cases where I create more than one GPA? What if I have a server that creates a thread for every request. Should I rather wrap a single allocator (e.g. a GPA) and then wrap it with a ThreadSafeAllocator and then use that allocator in every thread? Maybe with an ArenaAllocator around it, created in each thread? Or should/could each thread create an own GPA? What are the pros/cons?
Yet another question: Are there cases when I should use std.heap.page_allocator?
Thanks a lot in advance for helping me understand allocation better.
Maybe other newbies have similar questions when finding the right way to go when it comes to picking an allocator.