Saturday, February 26, 2011

Why we need better garbage collection on the Xbox

George Clingerman started a discussion on the App Hub where he asks developers what they would like George and other MVPs to discuss with the XNA dev team.

I took the chance to request that improvements be made to the garbage collector (GC for short) on Xbox. What's wrong with the current one, you may ask? Unlike GC on the PC platform, the Xbox GC is non-generational. A generational (also called ephemeral) GC distinguishes between newly allocated objects and older objects. Reclaiming memory early from "young" objects makes it possible to diminish the frequency of reclaiming memory from older objects, because many young objects are short-lived. This is important as reclaiming memory from older objects is an expensive operation.

To work around this limitation, Shawn Hargreaves has written articles on how to minimize or avoid altogether garbage collection during gameplay phases which must run at a constant 60 fps. To summarize, there are two ways: Make sure each collection is fast (the so-called low latency path) or avoid allocating memory to avoid triggering garbage collections.

The good thing with the first approach is that you don't need to worry much about allocations that happen "behind your back", i.e. allocations that are introduced by the compiler or the core library. The bad thing is that you must avoid complex object graphs. Note that this includes even objects allocated outside of the performance-critical parts of your code, such as menu code, art assets...

The second approach is by far the most popular. It works well with object-oriented designs which rely on mutability. This approach also has problems. Developers must avoid all kinds of allocation at all time during gameplay, which implies refraining from using many language constructs. This includes events, delegates and lexical closures in C#, tuples and closures in F#.

Having two options to avoid performance drops due to GC sounds nice until you realize you can't combine the two. You have to choose one or the other, but you can't mix the two. That's sad, because in practice an attractive design for games is to use OOP and mutability for menus and low-level array-based computations for gameplay.

And this is why we need better garbage-collection on the Xbox.

6 comments:

Simon said...

In order to prevent the GC from running you could also use object pools which is essentially what you have to do in java android games. However, by doing this you are effectively making your own malloc/free in C#.

Joh. said...

Bob Taco Industries has a nice post on their blog about techniques to avoid allocations. Object pooling is one of them, but it only works for the objects you control. Compilers also allocate objects, and they don't use pooling (that's what a garbage collector is for, right?). Avoiding compiler-generated allocations require:
1) knowing when your compiler allocates objects in your back,
2) praying this knowledge is stable and does not change with every new release of the compiler and the core library,
3) refraining from using useful features (e.g. computation expressions, see my posts on cooperative multitasking using the Eventually workflow).

1) I can manage, 3) makes me grumpy, 2) won't help me because I'm an atheist.

rei said...

Quick question... is there a reason you like to use ref fields more often in your classes than mutable?

Joh. said...

@rei: I think that's because I thought closures could not capture mutable fields, but it seems I was wrong. I just tried and it works.

rei said...

Ah, okay. I wonder if there might be a difference in memory pressure between the two, especially on Xbox.

Joh. said...

Yes, there is definitely a difference wrt memory pressure. Ref uses a reference-type to wrap the mutable value, whereas mutable does not.