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_allocator
is 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.OutOfMemory
is 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.GeneralPurposeAllocator
in 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.