Hello! In this post, I intend to detail some of the memory management techniques employed in Halogen. Bare in mind that the engine is still heavily under development, so this aspect of the engine in particular is likely to change as time goes on, but it’s also a topic that I think is important to address early on.
The main goals for the memory management system are pretty standard:
- Keep fragmentation to a minimum
- Organize objects in memory based on their lifetime and relevance to each other
- Maximize cache friendliness
- Provide debugging and troubleshooting facilities in the event of memory leaks or other memory related bugs
Right off the bat I want to mention Stefan Reinalter’s incredible series of posts on how memory is managed in his engine. The system he details in these posts served as the basis for the memory management system in Halogen. If you haven’t read these posts yet, here is a link to the first in the series.
Halogen’s memory management is based on the article above. To quickly summarize, memory is handled by a template class called an Arena. Arenas take a template for an allocator, a tracker, and a thread policy (in the future, they will also include bounds checking and other things, as detailed in the series). The result is an Arena for each region of memory your application uses; an Application arena, which handles allocating (and eventually deallocating) objects with application lifetime; a Frame arena, which can be used for any allocations needed during a single frame; and more. The Arenas can also be made to respect multithreaded calls to allocate and free memory. Here is an example usage:
// Create an instance of a fixed-length string in the app arena String<32>* myStr = HE_NEW(String<32>, g_ApplicationArena)("Hello!"); // Allocates a big object and aligns it to a 16-byte boundary BigStruct* bigObject = HE_NEW_ALIGNED(BigStruct, g_FrameArena, 16); ... // Later, delete those objects HE_DELETE(myStr, g_ApplicationArena); HE_DELETE(bigObject, g_FrameArena);
I hope to provide more information on this part of Halogen’s memory systems as they mature, but as you can see, they’re derived from the article linked above, which does a stellar job of explaining how it all comes together.
Early on, I decided that the engine doesn’t need to make use of growing/shrinking strings like
std::string. On the other hand, it’s not particularly friendly or easy to use
const char* everywhere, so I decided to make a simple fixed-length string implementation for the engine. The result looks like this:
String<32> myStr("Hello"); // supports plenty of operators myStr += " world!"; myStr = '_'; const char* rawStr = myStr.GetRawString();
I went with knowing the string size at compile-time in order to ensure that the raw array doesn’t have to be
new‘d in the string implementation – this avoids having to couple the memory arenas described above with the string.
For now, that’s all on memory management. Up next, I’ll talk about the roadmap for the engine as well as touch on the input binding system. Thanks for reading!