C# 的“收益回报”为我制造了很多垃圾。可以帮忙吗?
我正在使用 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
吗?
编辑:我发现这个问题关于如何迭代一个更广泛的问题资源池,不产生垃圾。许多评论者对此不屑一顾,显然不理解紧凑框架的局限性,但是 这位评论者更有同情心,并建议创建一个迭代器池。这就是我要使用的解决方案。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
编译器对迭代器的实现确实使用了类对象,并且使用(例如,使用 foreach)使用
yield return
实现的迭代器确实会导致分配内存。在事情的计划中,这很少是一个问题,因为要么在迭代时完成大量工作,要么在迭代时分配更多内存来做其他事情。为了使迭代器分配的内存成为问题,您的应用程序必须是数据结构密集型的,并且您的算法必须在不分配任何内存的情况下对对象进行操作。想想类似的生命游戏。突然之间,迭代本身就变得不堪重负。当迭代分配内存时,可以分配大量内存。
如果您的应用程序符合此配置文件(并且仅当),那么您应该遵循的第一条规则是:
例如,如果您有一个类似数组或列表的数据结构,那么您已经暴露了索引器属性和计数属性,因此客户端可以简单地使用 for 循环,而不是在迭代器中使用 foreach。这是减少 GC 的“轻松赚钱”,它不会使您的代码变得丑陋或臃肿,只是稍微不那么优雅。
您应该遵循的第二个原则是:
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:
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:
只是为了一笑,尝试捕获 Linq 查询中的过滤器并保留查询实例。这可能会减少每次枚举查询时的内存重新分配。
如果不出意外的话,语句
preallocatedComponents.Where(r => r.Enabled)
的代码要少得多,可以完成与您的收益返回相同的事情。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.