Hello! Quick question: I’ve always wondered what backing allocator to use when using an arena. In std.process.Init, it uses page_allocator, but I always use the default allocator for memory leak detection. What do you guys think? Or does it even matter?
there is no default allocator, you must mean DebugAllocator since that is the one with leak checking.
If you mean init.gpa that changes depending optimise, target, and libc linked.
It does not particularly matter, though DebugAllocator is particularly slow as it has a lot of book keeping that powers its debug features.
If you care you would profile your code with different ones and choose the best.
This is a question I’m struggling with as a career WebDev. xD
I’ve always been on the path of “worry about performance when it becomes an issue”. And while there’s nothing inherently wrong with that for personal Zig projects, Zig is designed to do more. If you’re working on a game engine, or in an embedded system with restricted memory, you have to be a lot more intentional about how you think about performance.
To answer your original question- it very much depends. Are you just trying to solve an AoC problem? Are you working on a toy project that only you will ever use? Or are you building something ambitious like a port of a major project ala git or sqlite?
Without knowing the answers to these questions, my general advice would be to use what Init gives you in your actual program, and use std.testing.allocator in your tests.
I would also say that learning the differences between the testing/debug allocator, the page allocator, and the FixedBufferAllocator is an important step on your Zig journey and in learning memory management.
For more context, I’m making a game and all my allocations are done with a bunch of arena allocators. Leak detection does help when developing in debug builds, which is why I use init.gpa as a backing allocator, and I’m fine with it using the smp_allocators in other builds. My question was: is using the page_allocator like std.process.Init as the backing allocator is better than init.gpa?
I prefer to write software using the simplest, most straightforward methods. It constantly surprises me how often a simple implementation delivers optimal performance. That’s why I don’t bother optimizing early until I’m absolutely certain that an alternative approach will give me a real performance boost.
Depends on what is better for you, backing with page_allocator is very fast.
And init.gpa is a safe default. In safe builds you will get the leak and free checks, in unsafe release modes you will get either smp_allocator or c_allocator if libc is linked, both of which are very fast.
If you are on wasm then init.gpa will always be the wasm allocator.
The only way to know is to measure. I wish it were easier than that but that’s the truth of it.
It is annoying that we get only a single arena allocator std.process.Init, when in practice it would be more useful to get a generally appropriate backing allocator for creating arenas, depending on build type etc.
The Init arena uses if (is_wasm) gpa else std.heap.page_allocator (see lib/std/start.zig), without an explanation why it does this specific switch instead of just using gpa everywhere; it makes it look like page_allocator may for some reason be better suited for arenas.
Yeah, that was exactly what I thought when I read start.zig. I thought that using page_allocator as the backing for the arena was the right choice or better suited.
And I agree,I’ve never written a program where a single arena was enough. I think the arena should not have been inside std.process.init itself.
I don’t understand the leak detection with an arena allocator. On arena allocator and fixed buffer allocator, free is a no-op unless you’re freeing the most recent allocation, so the backing allocator will basically detect that you did reset()/reset(.free_all) or not, and the leak detection will only tell you when it grew.
If you’re making a game, consider using a few fixed buffer allocator, one per lifetime scope (e.g global, stage, frame). Their buffers would come from the init.gpa.
For CLI that will do a job and exit, a bump allocator that grows might be the right tool for the job.
I’m still in the early stages of the game, which is mainly why I don’t want to commit to using a FixedBufferAllocator at least not yet. That’s also why I need leak detection with arenas, because the whole codebase is messy and I forget the best places to reset them.
For a small CLI making an arena is 2 lines of code:
var arena: std.heap.ArenaAllocator = .init(init.gpa);
defer arena.deinit();
And for anything bigger I always ignore the one that comes with std.process.Init.