Useful info here from a real world usage case: temporary-allocator-your-first-arena
The notion of a memory leak is different when comparing an explicit heap allocator (like context.allocator) to an arena allocator. Furthermore, the context.temp_allocator is a special arena allocator available to all scopes as long as they have access to context through the default “odin” calling convention or other means.
Let’s compare…
For an explicit heap allocator (like context.allocator), memory leaks (and bad frees) are more easily defined. If memory is allocated in a particular scope, and ownership is not passed on to another scope, the opportunity to delete that memory is gone, therefore a leak has no doubt occurred, and is track-able. One can safely assume a delete was forgotten or left out somewhere. Also, if that same memory has been deleted more than once, no doubt a bad free has occurred. There is very little ambiguity in this area.
For an arena allocator, the allocation of memory is more tied to a “lifetime” of your choosing. Multiple categories of “lifetime” may exist for an application, so multiple arenas may be required to fulfill each category. What is a leak when using an arena? That’s only definable by the developer who decides on what a “lifetime” means. There’s no way for Odin or a tracking allocator to know what the intended lifetime should be. To those entities the default lifetime is till program exit, which frees all memory at that time anyway. It is still possible to have a “classic” memory leak when using arenas if an arena is initialized in one scope, but ownership of that arena variable is not passed on to another scope, then the opportunity to free that memory is lost, and therefore a leak has occurred (maybe). Who’s to say if that was intended to satisfy a specific lifetime requirement. The ambiguity is left to the developer to resolve through logic. The added benefits are: 1. Clear lifetimes must (at least should) be defined, thus categorical analysis of memory usage by a developer is possible by tracing arenas. 2. There is no need to delete individual allocations, which usually require careful attention to how they were allocated. 3. There is no such thing as a bad free, since free_all() will free memory only if there is any to free.
How is context.temp_allocator special? It simply is a globally available arena independent of scope depending only on availability of context through the before mentioned default “odin” calling convention or other means. It’s use case is clear; if you are only allocating a little memory here and there for a short lifetime before program exit, and/or you don’t want/need to define your own arena, and/or don’t want/need to pass an allocator parameter around, then there is nothing wrong (leaks or otherwise) about using context.temp_allocator and not freeing it. However, it is a growing arena, so if it is used prolifically in many many loops, then you may need to make decisions on what “lifetime” that temporary data should have and free_all() where appropriate. Maybe consider defining your own arena instead in those cases.
So the ultimate answer is, no, you do not need to regularly free context.temp_allocator, unless you are using it in such a way that it may grow exponentially out of control. And if that’s the case, it may be best to define your own arena or use context.allocator to make tracking leaks more explicit. The context.temp_allocator will initialize a capacity of 4194304 Bytes (4.00 MBs) on the first time use. Most of the time, a program rarely ever uses more than a few hundred KBs of that, but if you find memory usage climbing closer to that 4 MBs, it may be time to rethink memory strategies. I believe the next capacity jump is a power of 2, so if needed, the next capacity level will be 8MBs, then 16MBs, and so on. Personally, if I get above 2-3 MB mark, I start to think about if I still want to use it or switch to a different approach. If I’m allocating hundreds of MBs or several GBs, I definitely will not use context.temp_allocator. I will either assign a specific arena for that large chunk, or use context.allocator so that I have a clearly defined handle to that memory.
You can check your usage like this (my tracker also prints these values
):
used := (^runtime.Default_Temp_Allocator)(context.temp_allocator.data).arena.total_used
cap := (^runtime.Default_Temp_Allocator)(context.temp_allocator.data).arena.total_capacity
I see fmt mentioned often when discussing context.temp_allocator. I’d like to point out that even though many of the commonly used fmt.print procedures do use context.temp_allocator, there is also:
- fmt.aprint series of procedures available if one wants to format a string with a specific allocator that has a different lifetime than context.temp_allocator.
- fmt.bprint series if one wants to use a [ ]byte buffer.
- fmt.sbprint series if one wants to use a string builder.