🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Memory Management in Real Games

Published October 11, 2008
Advertisement
Over the past week or so I've been working on a memory management system for our as-yet-unannounced project over at Egosoft.

We had several goals for the system, including the ability to debug common memory issues (leaks, double deletions, premature deletions, orphaned blocks, and so on) and present detailed information about what memory is going where.

During the course of this small adventure, I've run into several things that I thought would be worth sharing. Chances are this is going to only really affect people doing very large-scale projects (or console/handheld projects where memory is a premium resource, unlike on the PC). Also note that it's pretty much strictly C++ stuff, so those of you using real programming languages probably don't need to worry as much [wink]


Tip #1 - Start Early
We've left memory management until fairly late; a large block of code is already written, and all of my updates for tracking memory have required careful surgical manipulation of the 640+ file, 200,000+ LOC code base. Going into existing code, understandings its memory usage patterns, and then retrofitting memory budgeting and tracking logic is very painful.

This is ideally something that should be done almost before anything else in the game is coded; it affects literally every facet of the game, and it's always much simpler to build on what already exists than to build in a vacuum and come back later to fill in the gaps.


Tip #2 - Use Lots of Typedefs
You should always use a typedef instead of a standard container directly. For example, instead of spreading references to std::vector scattered everywhere, create a typedef std::vector WayPointVectorT and use WayPointVectorT everywhere.

This makes it much simpler to come back later and introduce STL allocators to your existing containers.


Tip #3 - Detail is Good
We've divided up our memory budget into about 20 different "types" of memory - ranging from file I/O buffers to graphics meshes to AI logic. Having a very detailed output of where memory is going makes it much simpler to see leaks. For example, if you know that the 300KB that's floating around belongs to the pathfinding system, you have radically narrowed down the amount of code you have to trace to find the leak.

In general, my rule of thumb here is pretty simple: if I can't decide what memory type a given chunk of memory belongs to, I create a new one just for that purpose. If implemented well, the overhead of this is extremely cheap, and precision is a very valuable tool when it comes to debugging.


Tip #4 - Create the Tools First, Then Teach People to Use Them
Inevitably on any project with more than one programmer you will encounter someone who is a bit behind the curve relative to their peers on the team. Instead of waiting for people to ask for a tool that would be useful, implement the tool anyways and teach people to use it as soon as possible.

This may seem like wasted time, but the rationale is really just common-sense: sometimes you don't notice you've been burned until you've had your hand stuck in the fire for a while. People won't recognize the need for tools in most cases; providing them as early as you can not only saves time, but improves the work environment overall.


Tip #5 - Implement it on a weekend
The best way to get something this significant and complex built into a pre-existing codebase is to get everyone else to temporarily check in all their code (make sure it compiles and runs first!), and work on the implementation while nobody else is active on the project. Merging delicate changes to memory management can be a nightmare, and often introduces unforeseen bugs and complications that will haunt you later.


Tip #6 - Take the opportunity to refactor a bit
During the course of implementing memory management, you'll begin to notice a lot of dependencies and interconnections between modules based on their memory usage behaviour. If any of these situations smell wrong, you've got a hand-crafted opportunity to correct them. Now you're improving the code base in two ways instead of one... so ask for a raise when you're done [wink]


Tip #7 - Plan your budget rigorously
We've been more or less flying on the seat of our pants with memory usage up until now. Unfortunately, we really need to get RAM usage under control, for a variety of reasons. This means we've planned on setting up a strict budget - each memory type is allowed only a certain number of bytes of allocation. Exceeding that size will fail and halt the game.

Just be careful not to invent the budget numbers on the fly. You'll be much better off if you divide your 1GB address space (or whatever your minimum spec says) into roughly correct blocks based on memory type. Without some planning ahead, it's easy to reach a situation where you've crammed in 9 out of 10 features, and the last one just totally thrashes your RAM constraints. So budget first, and then work on getting things into the correct space. Having a hard number of "it has to be this small" is very useful versus simply "uh oh, it has to be smaller somehow".



So there you have it - some little bits of drivel from the trenches. Now if you'll excuse me, I have 15 compiler errors to go fix.
0 likes 4 comments

Comments

VBStrider
Very interesting.

I haven't coded any memory management into my project yet, and like in your case it is already fairly large. After reading your journal entry, I've decided to add "memory management" to my to-do list (not to mention I have already ran into a couple memory leaks that I can't seem to locate).

I'm not sure how I'll go about writing the memory manager though... Do you have any good articles on the subject, or should I just start tinkering around?
October 13, 2008 08:59 AM
ApochPiQ
No articles, sadly; I just kind of hacked up something that works for our particular needs. I might write up a journal entry about it later on and describe the basic tool set.
October 13, 2008 09:39 AM
Zipster
Something we found very useful on our project was to keep track of where memory was being allocated by recording a trace of the stack when the memory was allocated. Then later we could use the debug help library to turn the addresses in the stack trace into actual symbol names. It's one thing to know that memory leaked, but knowing where it leaked is half the battle!
October 13, 2008 05:50 PM
Anon Mike
In kernel mode NT programming every allocation that uses the system allocator (ExAllocatePoolWithTag) has a 'tag' associated with it that can be used to identify who allocated the memory and for what purpose, this sounds like basically the same thing as your "types". It's incredibly useful for debugging.
October 13, 2008 06:43 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement