The other day a friend of mine was telling me about his XNA game. He’s a huge XNA fan. In fact, he’s almost sworn of C++ entirely. (His loss.) He was telling me about how he fixed this hiccup he had in his frame rate. He simply calls GC.Collect() every frame.
“Whoa,” I thought to myself. The red flag went up. Violate everything that the garbage collector does behind the scenes? I knew it sounded like a crutch, but I didn’t know why. I started a massive volume of research.
One of the first sites I came across was this one, Rico Mariani’s Performance Tidbits. Performance Tidbits? Sounded good. However, the site mostly talks about why each part of the GC.Collect call is bad business. It doesn’t really cover why we shouldn’t call GC.Collect every frame to reduce the garbage being collected each time.
That was quickly followed by this site, Pandemonium, which was another that simply asked us to reduce the amount of garbage being collected. It constantly referenced this book. (Effective C#: 50 Specific Ways to Improve Your C#) It was kind of interesting to see all of the various ways that garbage gets created without you knowing it. For example:
1 2 3 4 | string tempString = "Hello"; tempString += " "; tempString += "World"; tempString += "!"; |
Here you have created four strings; “Hello”, “Hello “, “Hello World”, and “Hello World!”. Now, that’s four strings that you will be collecting later, when you might have thought you’d only create one. Interesting tips, but not the answer to my question.
Continuing my search, I came across, How to Write High Performance C#, which was more of the same. The cool part of this article is that it covered some of the tools to use and some methods of benchmarking. Also, another tip for helping the garbage collector out by making sure that objects which will be destroyed are done so as quickly as possible. Objects go from generation to generation and get more expensive to collect if they live longer. There was another warning about calling GC.Collect. This seems fairly common, but no one thus far has really said why you can’t call it every frame to reduce the overhead of calling it.
A page or two later, I saw a site called Guidance Share. One small tip I saw here was an arguement for not calling GC.Collect ever.
The garbage collector is designed to be self-tuning and it adjusts its operation to meet the needs of your application based on memory pressure. Programmatically forcing collection can hinder tuning and operation of the garbage collector.
That sounds great and all, but it can’t possibly be the only idea behind it. Otherwise, it could be argued that by calling the collect every frame we’ve tuned the garbage collector to our needs and .Net can either follow our lead or get out of the way. We’ll do full collections every frame. (This still sounds bad to me.)
Then I stumbled upon this article from the .Net Compact Framework team, some of the guys behind XNA. It had a volume of good information, some if which seemed obvious or that I’d seen before, but it did have one little nugget about collection.
One thought that occurs to people is: “Why not call GC.Collect() every frame so it’s deterministic!”. The wasted overhead you get for “over collecting” typically doesn’t make sense.
At 60fps each frame takes about 16.67ms (that’s 1000ms/60frames). Isolated benchmarks show that a GC of 100,000 live objects takes about 14ms (though that’s a relatively large number of objects compared to Rocket Commander by Benjamin Nitschke which has just under 50,000 live objects at peak). Would you really want to sacrifice 80% of your game forcing GCs (~14ms/17ms)? Don’t think so.
So from the mouths of the XNA guys, the overhead is simply too great. I doubt his game is generating even 50,000 live objects (although with boxing, pass-by-value, and concatenation creating objects, who knows) so his overhead would not be that high. However, the basic principle remains. Why spend so much time EVERY frame collecting your garbage? In fact, why generate so much garbage every frame?
But please, don’t take my word for it. Any site on the internet (including mine!) is subject to criticism. If you have this problem, maybe you need to try generating less garbage, re-using objects, and reducing the amount of boxing / unboxing. Maybe you’re fine with calling GC.Collect every frame.
6 ResponsesLeave a comment ?
Hi – author of Pandemonium (your second link) here. Top work on digging deeper and deeper! I'm always uncomfortable with simply accepting the received wisdom as well.
I'd just like to add that you linked to part two of my GC thoughts… at the risk of shamelessly plugging my own articles, part one talks about everything that happens during a garbage collection. It doesn't add any more information to your final conclusion (don't call GC.Collect every frame because the overhead is too high), but it may give some insight into why the overhead gets too high – in fact, what exactly the overhead consists of. Link: http://bittermanandy.wordpress.com/2008/10/19/gar...
Thanks!
Yes! I saw the first article and coming from a C++ side, it was a very thorough review for me. The only reason I didn't link to that in the main article is that I wanted to keep the article focused.
Very good first article though, and I'm more than happy to have the further information in the comments. I recommend it for anyone who doesn't have a firm grasp on stack versus heap or even just the memory management of .Net.
In the string example, aren't you only creating three strings? "Hello" is a constant (like " ") and setting tempString to it will just alter what it refers to. Or is .NET so rubbish that a string gets copied when it's assigned to a variable?
Well, .Net does not keep a list of every possible constant string around. That would truly be the rubbish. However, I'm not quite sure what you mean. By typing "Hello" we have created a fully qualified instance of the "Hello" string. You can do "Hello".Length if you wanted. It's very awkward looking at first. (You can do 1.ToString() as well!) However, it's a string nonetheless. When you assign it to the tempString, it does not create an entirely new copy of "Hello", but you created a string.
However, the concatenation of the three strings does create three new strings as the string objects are changed:
string tempString = "Hello";
tempString += " ";// tempString is now "Hello "
tempString += "World";//tempString is now "Hello World"
tempString += "!";//tempString is now "Hello World!"
So in total, 7 strings exist, but 4 of those were string literals, so only three ones were created new.
You might be interested to see this explanation: http://www.reddit.com/r/programming/comments/acue...