Ska Software

Here's another post by someone who has learned everything through mistakes, not learning it right in the first place . :)

I've been working on some nasty GC issues on Xbox360 for some time now, and through RPM and CLR Profiler I've started to make some sense of things, so I thought I'd let everyone in on my experience so that the next guy (or gal) with these issues can have some info. So here's the story: Remote Performance Monitor showed 240 boxed value types per delta. Having no idea how I was creating boxed objects (or what they are), I ran the CLR profiler--as it would happen, I had 4 foreach calls per frame--for a postprocessing effect. According to CLR Profiler, the foreach calls were creating System.Collections.Generic.List<T>s. I switched

foreach (EffectPass pass in hazeEffect.CurrentTechnique.Passes)

to

EffectPass pass = hazeEffect.CurrentTechnique.Passes[0];

as there was only 1 pass in the effect anyway, and now I get 0 boxed objects per frame!

I guess the bottom line is that I never really used foreach calls, don't trust them, and now I've got better reason to! The part that was the culprit was a snippet I took from a tutorial.


The other GC issue I had was with tons of string.ToLower() calls. My character definition class gives animations string names, and each animation frame has a script; anyway, there were .toLower()s everywhere; I moved them all to load time and that's improved GC performance by about 3000ms per collection.


I guess the moral is avoid foreach and string.ToLower(), unless you know what you're doing, which I do not. Can anyone concur/provide the correct explanation



Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Kyle_W

I concur.  Good info and thanks for sharing.



Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Shawn Hargreaves - MSFT

Exactly right - both the things you identified will create garbage.

The foreach statement will create garbage because it needs to allocate an enumerator instance. If you use a disassembler to look at the code created when you do a foreach, a statement like:

foreach (MyType t in myCollection)
...

basically translates into:

IEnumerator<MyType> enumerator = myCollection.GetEnumerator();

while (enumerator.MoveNext())
{
MyType t = enumerator.Current;

...
}

For most collection types, the GetEnumerator method will have to allocate a new enumerator object, so this creates one piece of garbage per enumeration. Not a problem for things you only do a few times a frame (and I'm a big fan of foreach for the extra readability it can give) but this can be bad if you're doing it many times a frame.

If you think about it, the ToLower method is always going to create garbage, because it returns a modified version of the string, so it has to allocate new memory to hold this new version.





Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Kyle_W

Maybe the foreach statement should be removed from all samples in the XNA documentation since we've established that it's evil.



Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Shawn Hargreaves - MSFT

I'm not sure I'd call it evil. I use it a lot in my own games! This is one of those balancing things: is it worth making less readable code in the interest of better performance The answer really depends on your game. If you don't have GC performance problems, it makes sense to do things in the most readable way, but if that is too slow, obviously you have to do things differently to reduce garbage.





Re: XNA Game Studio Express The woes of boxed objects (and foreach)

thomas.aylesworth

It's also worth pointing out that enumerators are only created when using foreach on collections. Using foreach on an array doesn't create an enumerator, so there is no garbage collection penalty.



Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Nick Gravelyn

By collections do you just mean Collection<T> or do you also mean all the collection types like List<T> and LinkedList<T>




Re: XNA Game Studio Express The woes of boxed objects (and foreach)

thomas.aylesworth

Any type that implements the IEnumerable interface, which is all of the collection classes.

Basically, using foreach on an array is a special case since that's a built-in type.  In all other cases, the only way foreach can know how to iterate through the elements is by using the IEnumerable interface.

Edit: fixed interface name... whoops.





Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Kris Selden

when I compile not on the compact framework, a foreach loop through a List<ValueType> is optimized to be only local variables (stack alloc not gc):

// types enumerator as a ValueType instead of interface to prevent boxing
List<ValueType>.Enumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
  ValueType t = enumerator.Current;
  ...
}

You could also just use a for(;;). I don't think replacing the foreach with either of these is less readable and I personally don't think it is premature optimization. Though I do agree you should minimize upfront optimization, when you have domain knowledge of a problem I think a little upfront optimization is ok.





Re: XNA Game Studio Express The woes of boxed objects (and foreach)

Ska Software

I like for(;;) . Foreach just throws me because it's a bit automagic for my taste.

I'm a big fan of

for(int i = 0; i < array.length; i++)

having grown up on the VB

for i = 0 to UBound(array)