I'm looking for a way to write a memory allocator that will bucket allocations based on what system is using that memory (ie: have a bucket for UI, Animation, Gameplay, etc).

But the kicker is that I can't modify the call sites of any allocation, and the current allocator interface only asks for an alloc size and alignment.

I've had someone suggest manually walking the stack to figure out what's making the alloc call... are there any other options?

@khalladay Does that include preprocessor abuse around the existing callsites? i.e. Allocate(size,alignment) AllocateIntoBucket(size,alignment,kUIBucket)

Might be costly perf-wise, but what about a thread-local variable that keeps track of which is the current system, and then you add set/unset guards for that variable around the system boundaries?

Pretty brittle, but doesn't require code changes at call sites 🤷

@jon_valdes @khalladay I think this is a good option if your systems aren't calling into each other spaghetti style. Set an 'allocator context' right before entering a system. If there are separate allocators per thread the perf hit goes away. If not, well the allocators are already synchronizing anyway, right?

Macro trickery is my 2nd choice.

@abyrd @jon_valdes This seems sane.. but unfortunately the target engine is UE4, and I certainly don't know every entry point to every system (although I suppose you could set that variable at the beginning of every function? that feels dumb though?)

Am I missing something obvious here and assuming this would be hard to do on an engine the size of UE4 which was originally designed to have a single global allocator?

@jon_valdes @abyrd Actually, I think I'm going to give this approach a shot - it isn't feasible to expect it to catch everything UE4 does, but I think it might be enough to catch most of what we care about.

Something like a scoped_context object that we can wrap game code in that sets the allocator context for everything that the engine ends up doing because of our code.

Won't break down the memory very granularly, but all we want is coarse buckets to keep an eye on things for now.

Frostbite uses a system like this to only allow uses of malloc inside a scope where you have defined a "MallocScope", and it seems to work pretty nicely (otherwise it relies on explicit memory arenas for everything)

@jon_valdes @abyrd nice to know that I'm at least in the ballpark of things sane people have done before :)

How does it handle deallocation - I'm thinking of adding some extra header data to each alloc to identify what context it was from (we use a single global binned allocator, and ideally that won't change, so no explicit memory pools for us)

For freeing malloc'd memory, I believe it just uses the current MallocScope and assumes memory must have come from there (and asserts if it doesn't). For anything in arenas, I'm not actually sure, sorry

@khalladay Can you make a macro that replaces the current alloc interface and passes in __FILE__?

@joeld42 Definitely going to to do this for more fine grained memory investigation needs.

@khalladay if you’re in control of compilation, you should be able to inject macros into each system via compiler command line to replace calls to malloc/free and friends. Your replacement for malloc will likely need to pack a free function pointer into the header of each allocation so that any call to free can locate the system-specific free function or pool.

Sign in to participate in the conversation
Gamedev Mastodon

Game development! Discussions about game development and related fields, and/or by game developers and related professions.