C#的`yield return`给我带来了很多垃圾。可以帮忙吗?

时间:2011-04-29 22:52:49

标签: c# garbage-collection yield-return

我正在使用XNA开发Xbox 360游戏。我真的喜欢在几个地方使用C#的yield return构造,但它似乎造成了很多垃圾。看看这段代码:

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;
                    }
                }
            }
        }
    ...

我使用这些组件池到处 - 用于子弹,敌人,爆炸;任何无数和短暂的。我经常需要遍历它们的内容,而我只对活跃的组件(即Enabled == true)感兴趣,因此Components属性的行为。

目前,在使用这种技术时,我每秒钟会看到大约800K的额外垃圾。这是可以避免的吗?还有其他方法可以使用yield return吗?

编辑:我发现this question关于如何在不创建垃圾的情况下迭代资源池的更广泛问题。许多评论者都不屑一顾,显然不理解Compact Framework的局限性,但是this commenter更加同情并建议创建一个迭代器池。这是我将要使用的解决方案。

2 个答案:

答案 0 :(得分:3)

只是为了咧嘴笑,尝试在Linq查询中捕获过滤器并保持查询实例。每次枚举查询时,这可能会减少内存重新分配。

如果不出意外的话,preallocatedComponents.Where(r => r.Enabled)语句就会减少很少的代码来处理与收益率回报相同的事情。

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; }
        }
    ...

答案 1 :(得分:2)

编译器实现的迭代器确实使用了类对象,并且使用yield return实现的迭代器的使用(例如foreach)确实会导致分配内存。在事物的方案中,这很少是一个问题,因为要么在迭代时完成相当多的工作,要么在迭代时分配更多的内存来做其他事情。

为了使迭代器分配的内存成为问题,您的应用程序必须是数据结构密集型的,并且您的算法必须在不分配任何内存的情况下对对象进行操作。想想类似的Game of Life。突然间,迭代本身就是压倒性的。当迭代分配内存时,可以分配大量的内存。

如果您的应用程序符合此配置文件(并且仅适用于),那么您应遵循的第一条规则是:

  • 当更简单的迭代概念可用时,避免内部循环中的迭代器

例如,如果您有一个数组或列表之类的数据结构,那么您已经公开了一个索引器属性和一个count属性,因此客户端可以简单地使用for循环而不是使用foreach和迭代器。这是减少GC的“轻松赚钱”,它不会使你的代码变得丑陋或臃肿,只是不那么优雅。

你应该遵循的第二个原则是:

  • 测量内存分配以查看您应该在第一条规则中使用的时间和地点