Allocating large arrays in .NET

Published October 22, 2012
Advertisement
I experienced a strange memory issue with Lemma this week. Memory usage skyrocketed each time I loaded a level; it never dropped back down.

Now granted, I am definitely the garbage collector's worst nightmare. (I'll just say this: closures. Closures everywhere.) But at this point I am setting fields to null all over the place and manually calling GC.Collect() after unloading a level, all to no avail.

Enter the .NET Large Object Heap. See, the .NET garbage collector actually compacts the regular .NET heap by relocating small objects to fill the fragments. For exceptionally large objects, it's simply to expensive to relocate them, so the runtime allocates them on the Large Object Heap, which is not compacted.

I'm allocating huge 80x80x80 pointer arrays; definitely Large Object material. This means the virtual address space is not actually freed when the arrays are deallocated. This isn't quite as bad as it sounds, since the virtual memory system probably reclaims the physical memory. But eventually, I would hit the address space limit. Which I did on a few occasions.

My solution is to put the unused arrays into a "free" queue instead of letting the GC deallocate them. Then, when I need a new array, I first check if I can pull an old unused one off the queue, only allocating a new one if no old ones are available. This works for me because my arrays are largely the same size.

It's fairly easy to implement. Here's a rough approximation of my code before:

public class Map
{
public Box[, ,] Data;

public Map()
{
this.Data = new Box[80, 80, 80];
}
}

And after:

public class Map
{
private static Queue free = new Queue();

public Box[, ,] Data;

public Map()
{
if (Map.free.Count > 0)
this.Data = Map.free.Dequeue();
else
this.Data = new Box[80, 80, 80];
}

protected override void delete()
{
base.delete();
Map.free.Enqueue(this.Data);
}
}

Hope this helps if you stumble on the same problem.
1 likes 3 comments

Comments

Servant of the Lord
I used the same technique in C++ for faster level streaming, but for speed reasons instead of GC issues. The time-cost of the new() call for each 400-element array of pointers was expensive (since I needed multiple such arrays), but re-using the memory was alot faster and was required for seamless level loading.
October 23, 2012 02:38 AM
evanofsky
[quote name='Servant of the Lord' timestamp='1350959927']
I used the same technique in C++ for faster level streaming, but for speed reasons instead of GC issues. The time-cost of the new() call for each 400-element array of pointers was expensive (since I needed multiple such arrays), but re-using the memory was alot faster and was required for seamless level loading.
[/quote]

Did you ever read any of John Carmack's interviews about RAGE on iOS? He talks about literally blasting C++ objects straight from a file into memory, re-aligning the pointers, and going from there. I think RAGE relies heavily on memory-mapped files, which I thought was an extremely interesting OS feature I hadn't heard of before.
October 23, 2012 05:33 AM
mixmaster
Great read and some good references guy's. Keep up the great work.
October 23, 2012 08:52 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement