Ah, garbage collection. One of the high (and also low, low, low) points in favor of/against managed solutions like C#/XNA. High, because it frees you from having to lose sleep at night over your pointers and allocation and memory leaks you don’t know you have but are most certainly there somewhere, if only you could find them. Low, because you can optimize the hell out of your code and still take nasty performance hits from garbage collection you only indirectly control.
As with most things, I’m finding the more you understand about the labyrinthine and arcane workings under the surface of the tools, the less trouble you have with it doing unexpected things. Funny how that works out, isn’t it? What follows here is some of the things I’ve learned about avoiding major collection hits from my own sloppy code.
Actually, I lie somewhat, because I’ve learned this not so much from my own code as reading up on the topic before I manage to write the sloppy code. Mostly. This first one comes from Chad Carter’s excellent XNA book, XNA Game Studio 3.0 Unleashed.
If you are like me, you might be inclined to write a line of code like the above declaration in an effort to just get the stupid thing to do what it’s supposed to. Carter spends some fair time explaining how garbage collection works and why you should care, then trots out the above line as something of a case study. The TL;DR version? This trashes performance. Far out of proportion to expectation, most likely, and especially on the XBox360, where garbage collection and management is tighter than on the PC.
The why is pretty simple. Draw() gets called a lot—on the order of 30-60 times per second, depending on your framerate. The above code creates a new BasicEffect object every single time, resulting in a bunch of memory being allocated, used, and tossed into garbage over and over again for the exact same thing. Resulting in the code(especially on the 360) having to run collection frequently. And that is an expensive operation.
The real kick in the pants, is this will drop a 360’s framerate something like 90%(if you tell it to run at an unlocked framerate), but you probably won’t notice the impact until your code is complicated enough to drag it down under 60fps unless you’re doing some regular performance monitoring. Actually, I’d bet you wouldn’t notice until well under 30. Point is, by then, it’s a lot harder to track down where stuff is falling apart and why.
The lesson: don’t declare stuff in Draw() and Update().
But That’s not All!
Actually, it’s not that simple. Because we have to keep in mind another aspect of this, which I came across last night (I believe I read it in the preview for Charles Petzold’s upcoming WP7 book).
Specifically, it depends on exactly what you’re declaring. Objects (IE: classes) are a no-no, but structures are okay. This is due to garbage collection dealing with the heap, which is where objects end up allocated, whereas structures get allocated on the stack. So, while you’ll take a nasty performance hit for instantiating new objects in the main loops(or anything that they call regularly), you can get away with declaring yourself short-lived, repeated stuff on the stack just fine.
I haven’t actually delved into how true this is, myself, so I can’t speak to its veracity. But it seems to make sense, and Petzold has a history of being a well-informed authority on these sorts of things.
Bottom line is, it’s frequently the things that are meant to make our lives easier that we need to pay the most attention to.