I was looking for docs and examples about arena allocators and in the core collection there are two packages: core:mem
and core:mem/virtual
. Both of them have some arena-related procedures and I can’t quite understand the difference. I’ve found some examples that use “virtual” one but can’t really say why it was used. The package documentation also doesn’t say anything about “virtual” package so I can’t quite understand what’s the difference in the case of arenas and which one should I choose.
core:mem/virtual
is the package for virtual memory allocators, which is to say that they are allocators which specifically get their memory backing from the operating system’s virtual memory subsystem, who then distribute that memory through the Odin allocator interface.
core:mem.Arena
is an arena allocator, but it does not have any special handling for the memory that backs it. You initialize it with a slice of bytes, and it uses that slice of bytes to place its allocations inside. It does not expand the memory available to it; it merely uses what it is given.
core:mem/virtual.Arena
can optionally expand through the .Growing
variant, initialized with arena_init_growing
, or it can be .Static
, which is similar to core:mem.Arena
but the user does not need to provide memory for it. The .Buffer
type is the one most like core:mem.Arena
, in that the user does provide the memory to back it. In the latter two cases, both have bounded memory; it’s just the source of the memory that changes.
virtual.Arena
is a little fancier, and you can do more with it, but if you know the bounds of the memory you’ll be using and already have the memory allocated from somewhere, then mem.Arena
is the simpler option. I would say most people really only need virtual.Arena
in the case that they have an unbounded memory budget because it has the capacity to add more blocks from virtual memory as demand increases.
It would be cool if somebody did some examples using Arenas. I recentlyn discovered this year that Arenas can be used as a replacement to RAII but I cannot seem to find real world application examples, not toy examples.
Hello.
I have previously written about the use of allocators on my blog.
This is a my blog written in Japanese. If it is helpful.
Mythical Programming Language Odin. ( Centralized memory management system)
Briefly, the arena has a fixed buffer size, and the virtual has three types (fixed size, no size specified, and 1 Gbyte allocated by the system).
For versatility, virtual is probably better.
The temp_allocator
in Odin is an Arena allocator. They work well in situations where all allocations within share the same lifetime, which makes freeing them much easier. Everything goes at once; there are no individual free operations.
Alternatively, they’re good for temporary allocators, because the whole point is that you don’t care about how long the data is valid (thus you will never free it), so long that it at least lives long enough until the next allocation. In practice, temporary allocators hold many, many allocations until hitting their capacity and needing to reset.
In Odin, this is something that you control with free_all(context.temp_allocator)
, which is to say that you choose how long your temporary objects are valid. Again, this is just a flavor of shared lifetimes. Odin by default does not ever clear or empty your temp_allocator
, and in fact, it is a growable Arena, so it never hits any capacity limit outside of Out_Of_Memory
errors from the operating system.
Some examples:
- Per-frame allocations in a game.
- Per-benchmark allocations in a benchmark suite.
- Intermediate format string representations, such as made during logging.
- Other temporary allocations of any type.
If you want to see other examples of an arena being used, just search the Odin core for context.temp_allocator
.
Of note, one trick you can do with an arena allocator, which is also used in Odin under the name runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
, is to have checkpoints. Checkpoints are places where the arena is reset to some saved state after a certain scope, effectively creating linearly recursive lifetimes.
For example, you could set a checkpoint, allocate a few hundred objects for some intermediate calculation (like some kind of tree structure for pathfinding), get the result, then drop out of the checkpoint and allocate only one new object with the result and return that instead of leaving your intermediates in the arena space.
Chris Wellons has a good article on arena allocators written in C.
Arenas are the simpler (although more hands-on) solution to the lifetime problem that the Rust borrow checker tries to solve, more or less.