Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#'s `yield return` is creating a lot of garbage for me. Can it be helped?

I'm developing an Xbox 360 game with XNA. I'd really like to use C#'s yield return construct in a couple of places, but it seems to create a lot of garbage. Have a look at this code:

class ComponentPool<T> where T : DrawableGameComponent
    {
    List<T> preallocatedComponents;

    public IEnumerable<T> Components
        {
        get
            {
            foreach (T component in this.preallocatedComponents)
                {
                // Enabled often changes during iteration over Components
                // for example, it's not uncommon for bullet components to get
                // disabled during collision testing
                // sorry I didn't make that clear originally
                if (component.Enabled)
                    {
                    yield return component;
                    }
                }
            }
        }
    ...

I use these component pools everywhere - for bullets, enemies, explosions; anything numerous and transient. I often need to loop over their contents, and I'm only ever interested in components that are active (i.e., Enabled == true), hence the behavior of the Components property.

Currently, I'm seeing as much as ~800K per second of additional garbage when using this technique. Is this avoidable? Is there another way to use yield return?

Edit: I found this question about the broader issue of how to iterate over a resource pool without creating garbage. A lot of commenters were dismissive, apparently not understanding the limitations of the Compact Framework, but this commenter was more sympathetic and suggested creating an iterator pool. That's the solution I'm going to use.

like image 275
Adam Siler Avatar asked Apr 29 '11 22:04

Adam Siler


2 Answers

The implementation of iterators by the compiler does indeed use class objects and the use (with foreach, for example) of an iterator implemented with yield return will indeed cause memory to be allocated. In the scheme of things this is rarely a problem because either considerable work is done while iterating or considerably more memory is allocated doing other things while iterating.

In order for the memory allocated by an iterator to become a problem, your application must be data structure intensive and your algorithms must operate on objects without allocating any memory. Think of the Game of Life of something similar. Suddenly it is the iteration itself that overwhelms. And when the iteration allocates memory a tremendous amount of memory can be allocated.

If your application fits this profile (and only if) then the first rule you should follow is:

  • avoid iterators in inner loops when a simpler iteration concept is available

For example, if you have an array or list like data structure, you are already exposing an indexer property and a count property so clients can simply use a for loop instead of using foreach with your iterator. This is "easy money" to reduce GC and it doesn't make your code ugly or bloated, just a little less elegant.

The second principle you should follow is:

  • measure memory allocations to see when and where you should use with the first rule
like image 154
Rick Sladkey Avatar answered Sep 18 '22 12:09

Rick Sladkey


Just for grins, try capturing the filter in a Linq query and holding onto the query instance. This might reduce memory reallocations each time the query is enumerated.

If nothing else, the statement preallocatedComponents.Where(r => r.Enabled) is a heck of a lot less code to look at to do the same thing as your yield return.

class ComponentPool<T> where T : DrawableGameComponent
    {
    List<T> preallocatedComponents;
    IEnumerable<T> enabledComponentsFilter;

    public ComponentPool()
    {
       enabledComponentsFilter = this.preallocatedComponents.Where(r => r.Enabled);
    }

    public IEnumerable<T> Components
        {
        get { return enabledComponentsFilter; }
        }
    ...
like image 30
dthorpe Avatar answered Sep 21 '22 12:09

dthorpe