Hi,
I’m working on a pretty complex game. At it’s core lies an AI system that simply uses a lot of objects and a lot of memory. There’s not much I can do about that. And the amount of memory will increase as I expand the domain.
Due to the large number of objects (I presume) the GC has a hard time - spikes are around 500ms on a powerful machine. I tested with the 2019 GC with mixed results. It makes the spikes almost tolerable however I’m worried about them increasing as I add more stuff to the game.
So my best approach seems to be to head to zero (or close as I can get) memory allocation. Getting to this conclusion was a long journey that ended in a predictable place.
I mainly see two tehniques to lower memory usage (beside the usual don’t use strings etc etc).
-
“Global” variables. Ie. variables that even though used locally in a method you declare at the system or component level. That way we don’t need to keep reallocating them every time the method is called. It’s dirty coding but let’s say tolerable as long as you’re careful about recursive methods and so on.
-
Where you can’t “global” something you need to pool it. And here lies the trouble. Pooling means:
a. Writting a pool system - fair enough, no biggie
b. Using the pool system to get new objects instead of calling new - again, fair enough, a little factory pattern never killed anybody
c. Returning the objects to the pool once they’re no longer used - well wait a minute - this is the exact same situation as if we didn’t have a memory manager - we just killed all/any productivity benefits the GC could bring us
d. Each poolable objects needs to have a reset() method. That is, once it’s returned to the pool you need to bring it to a pristine state before you return it as a new object next time someone wants an object of that type.
Now if you’re pooling lists you’ll just call Clear() on them and you’re done - easy peasy.
But what happens when you pool complex objects - like components (from an ECS system). Those are objects that can have 30-40 properties - some value, some reference types. Your reset method needs to set the default value for each property for value types and somehow reset the reference types if/when possible (ie. List.Clear() etc).
You have 50 components, you do it once, no biggie. But now you have to remember - every time you add a new property to a component - you now have to add it to the reset() method as well. You forgot? Good luck figuring that elusive bug 3 months down the road. And good luck training new team members on all the hacks.
This just seems to be a very very poor way to write code. I see no advantage over manual memory management - you still have to “free” your stuff. I see a lot of extra code, complication and maintenance issue caused by the GC. (Yes, I know it guards against memory leaks - I have a lot of experience writting manual memory managed stuff - there are tools to help, and memory leaks just aren’t a big problem with clean code.)
Sorry for the rant, I’m just hoping someone will point I’m missing something obvious and taking a hilariously wrong approach. GC for game development just seems very inferior to manual memory management.
A GC that would work as the default but allow you also manage memory (ie. letting the GC know not to concern itself with an object and giving you a way and the resposibility to free it) would be awesome!
Edit: can’t find a way to fix the typo in the subject :))