Search
Close this search box.

WP7, XNA, the GC, and You (and Silverlight too!)

Some of you may have come across this helpful blog post recently, discussing when the garbage collector (a/k/a the GC) runs on Windows Phone 7 devices. Anyone who has programmed in XNA for the Xbox 360 before knows the GC well. For those of you new to XNA, there are several tricks and tips for slaying the evil GC monster that eats frames. This applies to Silverlight apps too, though since Silverlight does quite a lot for you automatically, there’s only so much control you have.

First, you must leave this site. Not permanently, but just temporarily to go read this post by Shawn Hargreaves: http://blogs.msdn.com/b/shawnhar/archive/2007/07/02/twin-paths-to-garbage-collector-nirvana.aspx.

One of the first things you must understand is the difference between value types and reference types. Value types such as int, float, Vector3, Matrix, and struct (this includes nullable types, btw – a nullable type like bool? is just a special struct) live on the stack. The GC does not care about the stack. Well, technically it cares slightly, but only to the extent that the system begins to run low on memory, and you would have to be trying awfully hard to get enough items on the stack to cause the system to run low on memory. So don’t worry about calling “new Vector3()” or “Matrix.CreateTranslation()” in your methods that run regularly (like Update and Draw) – it’s just a stack allocation and it won’t anger the GC.

Classes are an entirely different matter. Classes, arrays (including arrays of value types, e.g. int[ ]), collections (List<>, Dictionary<,>, etc.), and strings (yes, strings) are all reference types and they live on the heap. The heap is the GC’s turf. It pays attention to everything that shows up on the heap and to everything that no longer has any business but is still hanging around. The latter are things that have “gone out of scope”. As an example, in this bit of code:

void CheckForTrue(bool value) {
  string trueText = "The value is true.";
  string falseText = "The value is false.";

  if (value == true) {
    Console.WriteLine(trueText);
  } else {
    Console.WriteLine(falseText);
  }

  return;
}

every time this method runs, trueText and falseText will both be allocated on the heap and will both “go out of scope” when the method is finished running. In other words, gone out of scope just means that there are no more references to an object. A string declared with “const” never goes out of scope and thus doesn’t matter to the GC for all practical purposes. This is also true of any object declared as “static readonly” since once it is created it exists forever. The same is not true for a normal “static”, though many might mistakenly assume so. A “static” object without the “readonly” keyword applied to it will generally exist for the life of a program. However, if it is ever set to null, then unless there is some other reference to it, it goes out of scope and is subject to garbage collection.

As you’ve read in the first article, on WP7 (and on the Xbox 360), the GC runs for every 1 MB of heap allocation. Whenever the GC does run, it takes time to comb through the heap and destroy any objects that are no longer in scope. Depending on how many references you have and how complex your nesting of objects within objects within objects is, this can take a bit of time. In XNA, the clock is on a fixed time-step by default and on WP7, the default frame rate is 30FPS. This means that there are 33.3333333 milliseconds available for Update and Draw to finish their CPU-side tasks. (Draw prepares things on the CPU-side then hands over the actual drawing to the GPU which, being a separate processor, doesn’t usually affect the Update/Draw side of things – except for stalls, but those are beyond the scope of this post and most people will never run into them anyway). If they finish ahead of time, the CPU hangs out and waits until it’s time to run Update again. If not, then the system takes notice that it’s running behind and will skip as many draws as necessary to catch back up.

This is where the GC comes in. Normally your code will complete just fine within the 33.33 milliseconds, thereby maintaining a nice even 30FPS (if your code doesn’t normally complete within that time you’ll see serious constant performance problems that may even cause your game to crash after a little while if XNA gets so far behind that it throws up its hands and surrenders). However when the GC runs, it eats into that time. If you’ve kept the heap nice and simple (the second path in Shawn Hargreaves’ post up above), the GC will run nice and fast and this likely won’t matter. But keeping a simple heap that the GC can run through quickly is a difficult programming task that requires a lot of planning and/or rewriting and even then isn’t fool proof (sometimes you just have a lot of stuff on the heap in a complex game with many assets). Much simpler, assuming you can do it, is to limit or even eliminate all allocations during gameplay. Note that I said during gameplay. You’ll obviously be allocating heap memory when you first startup the game (e.g. when loading assets in the LoadContent method), and you’ll be allocating memory when loading levels if you have a game with levels and decide to load each one in an interstitial screen. You’ll also be allocating memory when changing GameScreens if you use the Game State Management sample (phone version) as your starting point (and I recommend at least considering it – it’s a very nice starting point for a game, though you’ll want to customize it of course). But a small stutter from a couple of dropped frames in between levels or while switching screens isn’t a big concern – the player isn’t going to accidentally fall off a cliff or get hit by an enemy projectile or anything when those things are happening. In fact, sometimes it makes a lot of sense to intentionally trigger the GC right before the game is going to (re)start. Triggering the GC resets the 1MB counter and can prevent situations where the counter is at .94MB when the level begins such that even a small number of minimal allocations that would otherwise be perfectly acceptable can cause problems.

So the goal is to minimize heap allocations. How do we do that? Well, the biggest contributors are needlessly creating new objects in your Update/Draw cycle and boxing value types. First a quick note on boxing/unboxing. The simplest example of boxing is casting a value type like int or enum to “object” in order to pass it as a state. Rather than try to explain it further, I’m instead going refer you to the C# Programming Guide’s entry on Boxing and Unboxing. Boxing is a great feature of .NET, but is very bad for game programming because of the heap allocations that can trigger the GC. So keep an eye out for it and try not to do it.

The other big contributor is creating new reference types. Every new instance of an object causes a heap allocation and increases that counter ever so slightly. There are several coding practices that will help you to eliminate needless heap allocation and create better performance for your game.

  1. Make any strings that never change into const strings.
  2. Where you do need strings that change, consider using System.Text.StringBuilder instead. All XNA methods that take a string (e.g. SpriteBatch.DrawString) will also take a StringBuilder object. Make sure to use one of the constructors that takes a default capacity and set it high enough that it will hold as many characters as you plan to ever have in it plus a few extra for good measure. If the internal array is large enough, it will never have to resize it self and thus will never generate any heap allocations after it is created!
  3. If you need to draw an int value such as a score or the number of lives a player has left, consider using this great extension method from Stephen Styrchak, one of the XNA team members at Microsoft.
  4. If you have a class for, e.g., projectiles, create an object pool to reuse them rather than letting them fall out of scope and creating new ones each time one ceases to exist in the game and each time you need a new one. As an example create a generic List<> of your projectile class. Use the List<> constructor overload that takes a default capacity and make sure to set it high enough to contain all the objects of that sort that will ever exist at one time in your game (e.g. 300). Then use a for loop to go through and create all of the objects in the list up to the capacity. Add a “public bool IsAlive { get; set; }” property to your class to keep track of which ones are being used at any particular time. When you need a new one, loop through the list until you find one where IsAlive is false. Take that one, set IsAlive to true, set the other properties (such as its position, direction, etc.) to their appropriate values, and continue. When doing collision detection, loop through using a for or a foreach loop and only process the objects for which IsAlive is true. Same goes for updating them and for drawing them. Whenever one is no longer needed (e.g. it collides with something or it goes off screen), simply set its IsAlive to false and it’s now available for reuse without any memory allocation. If you want to be creative, you can expand on this further in several different ways. You could keep a count of the number of live objects so that once you’ve processed that number in your update and draw methods, you can use the “break” keyword to get out of the loop early rather than go all the way to the end. Or you could keep two lists, one of alive objects and one of dead objects and move objects between the two lists as appropriate. Or your could use what my friend Brian calls a recycle array to automatically keep a “sorted” array in which live objects are all kept at the front.
  5. When using enums as keys in a dictionary, make sure to implement your own IEqualityComparer<T> for the enum using that great example from Nick Gravelyn, another XNA team member. Without creating and using your own IEqualityComparer<T>, .NET will create a generic one for you and the generic one it creates will box the enum, creating heap trash that will eventually bring the GC to life. With it, though, no trash!
  6. If you do want to create something you can just create a new “instance” of each Update or Draw, try creating a struct instead of a class. Structs can do most of the same things that classes can (the major limitation being that they cannot inherit from another struct or a class or anything else (but they can implement interfaces)) and struct live on the stack, not the heap and so unless you have a reference type like a string or a class as a field or property of the struct, you will not generate any trash using a struct. Remember, though, that an array of structs is a reference type (as are all arrays) and thus lives on the heap and counts towards the GC trigger limit whenever created.
  7. Do not use LINQ. It looks cool. It makes your code shorter, simpler, and perhaps even easier to read. But LINQ queries can easily become a big source of trash. They’re fine in your startup code since you’re going to generate trash there anyway just by loading assets and preparing game resources. But don’t use it in Update, Draw, or any other method that gets called during gameplay.
  8. Minimize use of ToString(). At a minimum it creates a string, which lives on the heap. See above about how to draw an int to the screen without generating any garbage. If you do need to use ToString, try to limit how often it’s called. If the string only changes every level, only generate it once at the beginning of the level. If it only changes when a certain value changes, only generate it when that value changes. Any limits you can put are worth it. The amount of time it takes to check a boolean condition is so small as to be almost nonexistent. You could probably fit tens and even hundreds of thousands of true/false checks in the amount of time it can take the GC to run on a complex heap.

That’s all the tips and tricks I have for now. There are others I’m sure you’ll think of once your mind is properly tuned in. One nice feature of XNA is that it makes it very easy to create cross-platform versions of your game. Oftentimes, it’s as simple as going to the “Project” menu and selecting “Create Copy of MyMagicGame for _________”. Input might take a little bit of tweaking, and you might want to become familiar with the #if WINDOWS and #if WINDOWS_PHONE preprocessor directives to keep code that only belongs in one version just in that version. But by doing so, you can use tools like CLR Profiler to examine memory allocations to help find sources of garbage, and you can use sampling profilers like ANTS or VSTS to see where CPU time is being spent. Have a look at another one of Shawn Hargreaves’ posts, the sarcastically titled “Why measure when you can guess?”, for more information about how to find out why your game might be running slow at certain places so that you can focus your optimizations on the right things. There will be some differences between Windows and Windows Phone 7, of course, but some of the techniques he recommends, including one that’s not on that page – profiling with Stopwatch – will help you see what’s going on directly on the phone itself. Also remember that your emulated graphics, especially if you have a really fancy graphics card in your computer, may actually be quite a bit faster than the GPU of the phone itself. So be wary of really pushing the limits of the emulator and make sure to leave room to scale back if it proves necessary when you finally get your hands on a real phone to test with (by the way, if anyone has a spare one they need stored somewhere… 🙂 ). And if you haven’t picked up on it yet, Shawn Hargreaves’ blog on MSDN is one you’ll want to bookmark, subscribe to, and read thoroughly and regularly. Even posts from 3+ years ago are relevant and useful today!

Thanks for reading, go create some great games (and Silverlight apps – remember these heap allocation minimization techniques transfer over into your code-behind code for performance sensitive Silverlight apps too), and good luck!

This article is part of the GWB Archives. Original Author: Michael B. McLaughlin

Related Posts