Live from PDC – Real Games Analysis and Optimization of XNA Framework Games for Windows Phone

PDC is on, and you can watch it online. Right now I am attending Jeff Petkau’s Real Games Analysis and Optimization of XNA Framework Games for Windows Phone session. Jeff’s talk is after Shawn Hargreaves’ session. While Shawn’s talk addressed XNA Framework game design and techniques to improve load time along with overall graphics and audio tips, Jeff’s talk focuses on a more specific approach to code that is required to yield a performing XNA Framework game for Windows Phone.

This is NOT an introductory talk. Jeff assumes that you are familiar with the XNA programming model, .NET, and some aspects of how CLR (common language runtime) works. With that said, even if you don’t know XNA at all, this is a very good session, especially if you are planning on writing XNA games for Windows Phone. However, if you are not at all familiar with XNA, please make sure you visit the XNA Framework education roadmap before viewing the session, and learn how to create XNA games for Windows Phone.

Jeff is part of the Advanced Technology Group (ATG), which specializes in helping publishers release games for Xbox and now Windows Phone. Jeff has tons of experience, and shares the real world gaming analysis and tips and tricks he painfully learned over the years–making this a great talk

The two main performance problems for XNA games on Windows Phone are Garbage Collection (GC) and low frame rate. Some of the problems that developers face are related to phone hardware limitations. The phones use a 1GHz Snapdragon processor, have a minimum of 256 MB of memory, and run .NET Compact Framework (.NETCF), a lightweight version of the full .NET for Windows.

While the .NETCF supports a large subset of the APIs found in the regular .NET framework, there are few differences. The .NETFC JIT compiler (known as JITer) and the Garbage Collector (GC) differ from their counterparts in the full .NET framework. For example the .NETCF JITer is optimized to run fast, but not to produce the fastest code possible. The .NETCF GC doesn’t support generations, but performs a single pass through the entire process memory heap, finding all the roots into the GC heap. Starting from these roots, it visits every object and follows every object pointer contained in every visited object, marking the objects as it goes along. In this way the collector finds every reachable or live object. The other objects, the unreachable ones, are now condemned for collection. You can think of the .NETCF GC process as being very similar to the .NET full collection process.

But, even with these limitations, you can produce amazing games such as Harvest, Star Wars: Battle of Hoth, Ilomilo, and many more. You can read about most (if not all) Windows Phone games in http://www.bestwp7games.com/. All it takes is some knowledge and an understanding of the limitations.

Back to Jeff’s talk: Due to CPU and .NETCF limitations there is one key thing you need to be aware of–the GC runs might result in significant performance hits to your game. From this point on in the presentation, Jeff shows how to code in order to address some of these problems. However, as Jeff puts it, some of his solutions are not in keeping with .NET best guidance, but you may need to compromise on code maintenance and readability in order to achieve killer performance in your games.

Based on empirical testing and profiling large numbers of Windows Phone XNA applications, each 1MB of heap memory is equivalent to about a 10 msec run of the NETCF GC. Wow! I don’t know what you think, but 10 msec per each 1MB seems like a lot of time to me. If you have a 50MB heap, you are talking about 500 msec (half a second) per each 1 MB that you add to the heap. That is a lot of time and most likely will not get you to 33 frames per second. But you need to remember that we are talking here about the phone’s “limited CPU” (by limited CPU, I mean in comparison with an Xbox game console or Windows desktop computer).

The solution is: DO NOT CREATE Garbage (go green!). Seriously, during your game play, which is the time that the user plays the game (not including menus and such), you can’t allow any GC. You must be 100% certain that your code doesn’t generate new heap allocations that exceed 1MB or else the GC will start collecting for the entire heap, which can be VERY bad.

To test this, either run your code on a Windows Phone device or, as Jeff suggests, create a Windows build of your application (super easy), and use Visual Studio profiling tools to test for managed code memory allocation. You can’t really check CPU performance, but memory should behave similarly. Find where you create garbage during your game and fix it. You should find your usual suspects, such as strings using non-XNA types such as float[3] instead of Vector3, using LINQ, or using a foreach loop (the yield is expensive in terms of garbage collection). Once you fix your code, you should see no GC runs during your game time, which would be very good.

Jeff’s next topic is low frame rates. Low frame rates can be tricky as you don’t know where the bottleneck is. Is the low frame rate caused by the too much CPU work, or are you pushing too much content down the graphic pipeline so that the CPU can’t keep up? So what can you do?

  • To test why you are experiencing a low frame rate, you need to instrument your code–add System.Diagnostics.Stopwatch around your Update() and Draw() methods
  • Since you might have multiple problems, you need to remember to test Update and Draw separately to make sure you identify if your problem is CPU or GPU bound, and disable Update while drawing – since Update is mostly CPU bound
  • You can also use Scaler to reduce the number of pixels you draw in order to find out if your problem is in the fill rate due to drawing too many pixels to the screen.
  • If your game is running much faster (like several times faster), than you’ve found your problem

Jeff demonstrates even more tips and showed additional tools, so I highly recommend this session.