4
\$\begingroup\$

I am going over the memory architecture for my game and even though I know my memory budgets may not be final, I am at the point where I can start using them in a general sense. I know that I will need several allocators (for systems such as audio, rendering, etc) but I am unsure of how they will be accessed. I do not use singletons, so I can't just have something such as AudioManager::GetInstance().get_allocator(). Instead, I need to find a different method of usage but I am not sure of how.

How can I store and call my allocators needed for several different systems over engine in an efficient manner?

\$\endgroup\$
2
\$\begingroup\$

There are two ends of the spectrum here:

  1. You can do the simple thing, and make the allocators globally-accessible things (free functions, global objects). In this case you rely on the discipline of the consumer to use the correct allocator for the subsystem in question, by calling the appropriate function or passing the appropriate allocation bucket enumeration, or whatever.

  2. You can make the allocators instance objects, disabling the usual memory allocation functions (malloc, new et cetera), and require all allocations to route through one of these allocator instances. You then pass these instances around to anything that is permitted to allocate memory.

The first option is straightforward, and probably the most common. It does require some minor discipline to avoid, say, using the rendering allocator in the physics subsystem, but this is rarely going to be a challenge. It plays well with code where you can't change the interfaces that much, because you don't have to introduce new parameters to shuttle allocators around. It's easier to bind up with macros to automatically pass __FILE__ and __LINE__ for diagnostics. But these are global points-of-entry into the allocation subsystem, in the end, which can hurt you. Particularly where multithreading is concerned.

The second option is interesting in that it lets you really strictly control who can allocate, and when. It's arguably slightly easier to build stack allocation (allocations in a stack, not allocations on the C++ stack) paradigms around. There are "no globals," which regardless of how you feel about them can be an improvement in the ability to write safe concurrent code. But it can also be cumbersome. The Stingray engine took this approach to the extreme of mandating allocator instances be passed even to basic containers (equivalents of std::vector), which has knock-on implications for the default-constructibility of a lot of stuff and can be tedious to work with in practice, leading to some "temporary allocator" kludges that require more discipline to use correctly, without circumventing the rules about where and when you can allocate.

Practically speaking, you'll likely want to be closer to #1 than #2; even though the latter is interesting I think it's a little too rigid, at least when taking to the extreme where literally everything must have an allocator reference to construct.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.