It has been some years since I wrote
two articles on memory usage. The series was called “monitoring
your console’s memory usage” and was featured on Gamasutra. The
articles discussed how I built a tool to monitor memory fragmentation,
memory consumption and memory leaks. When I was almost done writing
the articles I had an idea how to greatly improve the concept of the
tool.
Shortly after, I started a new version
of the tool and a few months later it was finished. Now, years after
building the tool, the renewed concept still seems very strong and
proved to be useful for the titles I have worked on. We even managed
to locate memory leaks in the Xbox API and TechCertGame - the
Xbox sample that serves as a sample for a fully technically certified
game.
In this article I will discuss the
concepts of the memory analysis tool that I developed during the time
I was working at Playlogic. I surprisingly named it MemAnalyze 2.0 –
I’m not particularly creative in thinking up catchy names.
We will start off with the concept,
then we will see some of the features of the tool and finally we will
dive into some of the tricky implementation details. The previous
articles covered implementation details for Xbox and PS2. This time
we are going to look at the pitfalls for monitoring memory on PC.
The MemAnalyze concept
We will quickly recapitulate the old
concept. If you want to, you can read the details in the previous
articles but it isn’t necessary to understand the remainders of
this article.
We begin by intercepting all memory
allocations in the game. For each allocation that is performed,
metadata is saved in memory, most notably callstack information. At
any moment in time the user can select to write all allocation data
to file. The file serves as input for our MemAnalyze tool. The tool
can analyse the data and offer multiple views on that data.
This concept has some drawbacks:
-
Monitoring memory usage is limited
to a single moment in time, a snapshot of the memory statistics of
the game.
-
Storing metadata in the
application that is being monitored clutters your memory statistics.
In my test environment, which is an average sized commercial game,
the top of the allocation count is roughly half a million
allocations. About 80.000 of those allocations were performed by
operator new.
The size of the metadata of an allocated block differs per block
because of variable callstack lengths, but one of my tests shows
that on average 126 bytes are needed per allocated block. Note that
in this test 32 bit addresses were stored in the callstack. This
means that approximately 77 megabytes of additional memory is needed
for a memory map containing all metadata per block. If only the
allocations from operator
new were stored, we would need approximately 9.5
megabytes.
To remove both restrictions, we now
instead send any allocation data directly over the network. A tool
that runs on a PC then gathers the allocation mutations and maintains
an internal memory map. This tool can then do real-time analysis on
its internal memory map.
That’s actually all there is to it.