Olh*_*sky 25 c# iteration garbage-collection compact-framework
我在实现IEnumerable接口的对象池中有以下代码.
public IEnumerable<T> ActiveNodes
{
get
{
for (int i = 0; i < _pool.Count; i++)
{
if (_pool[i].AvailableInPool)
{
yield return _pool[i];
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知(根据这个问题),这将产生垃圾,因为需要收集IEnumerable对象._pool中的所有元素都不会被收集,因为池的目的是保持对所有元素的引用以防止垃圾创建.
任何人都可以建议一种允许迭代_pool以便不生成垃圾的方法吗?
在池上迭代时,池中的所有项都AvailableInPool == true
应该迭代.订单无关紧要.
Eri*_*ert 47
首先,一些人正在推翻奥尔霍夫斯基,暗示这一点都不用担心.在某些环境中的某些应用中,避免收集压力实际上非常重要.
紧凑的框架垃圾收集器有一个简单的策略; 每次分配1000KB内存时,它会触发一个集合.现在假设您正在编写一个在紧凑框架上运行的游戏,并且物理引擎每次运行时都会产生1KB的垃圾.物理引擎通常每秒运行20次.所以这是每分钟1200KB的压力,嘿,这已经超过了物理引擎每分钟一次的收集.如果该集合在游戏中引起明显的口吃,那么这可能是不可接受的.在这种情况下,您可以采取的任何措施来减少收集压力.
即使我在桌面CLR上工作,我也是在努力学习.我们在编译器中有一些场景,我们必须避免收集压力,我们正在跳过所有类型的对象池箍来实现这一点.奥尔霍夫斯基,我感受到你的痛苦.
那么,为了解决您的问题,如何在不产生收集压力的情况下迭代池化对象的集合?
首先,让我们考虑为什么在典型情况下会发生收集压力.假设你有
foreach(var node in ActiveNodes) { ... }
Run Code Online (Sandbox Code Playgroud)
从逻辑上讲,这会分配两个对象.首先,它分配可枚举 - 序列 - 表示节点序列.其次,它分配枚举器 - 光标 - 表示序列中的当前位置.
在实践中,有时你可以作弊,并有一个对象代表序列和枚举器,但你仍然有一个对象分配.
我们怎样才能避免这种收集压力?想到三件事.
1)首先不要制作ActiveNodes方法.使调用者通过索引迭代池,并检查自己该节点是否可用.然后序列是已经分配的池,并且光标是整数,两者都没有创建新的收集压力.您支付的价格是重复的代码.
2)正如Steven所建议的那样,编译器将采用任何具有正确公共方法和属性的类型; 它们不必是IEnumerable和IEnumerator.您可以创建自己的可变结构序列和游标对象,按值传递它们,并避免收集压力.拥有可变结构是危险的,但这是可能的.请注意,List<T>
将此策略用于其枚举器; 研究其实施的想法.
3)正常地在堆上分配序列和枚举器并将它们集中在一起!你已经采用了汇集策略,所以没有理由不能集合一个枚举器.枚举器甚至有一个方便的"重置"方法,通常只会引发异常,但是你可以编写一个自定义的枚举器对象,当它返回池中时,它使用它将枚举器重置回序列的开头.
大多数对象一次只能枚举一次,因此在典型情况下池可能很小.
(现在,你当然可能会遇到鸡和蛋的问题;你如何列举一下普查员库?)
Ste*_*ven 22
迭代项将在任何"正常"设计中始终导致创建新的可枚举对象.创建和处理对象非常快,因此只有在非常特殊的情况下(低延迟才是最重要的)垃圾收集可能(我说'可能')成为一个问题.
通过返回未实现的结构,可以实现没有垃圾的设计IEnumerable
.C#编译器仍然可以迭代这些对象,因为该foreach
语句使用duck typing.但同样,这不太可能对你有所帮助.
UPDATE
我相信有更好的方法可以提高应用程序的性能,而不是产生这种低级别的技巧,但这是你的号召.这是一个struct enumerable和struct枚举器.当您返回可枚举时,C#编译器可以预先通过它:
public struct StructEnumerable<T>
{
private readonly List<T> pool;
public StructEnumerable(List<T> pool)
{
this.pool = pool;
}
public StructEnumerator<T> GetEnumerator()
{
return new StructEnumerator<T>(this.pool);
}
}
Run Code Online (Sandbox Code Playgroud)
这是StructEnumerator
:
public struct StructEnumerator<T>
{
private readonly List<T> pool;
private int index;
public StructEnumerator(List<T> pool)
{
this.pool = pool;
this.index = 0;
}
public T Current
{
get
{
if (this.pool == null || this.index == 0)
throw new InvalidOperationException();
return this.pool[this.index - 1];
}
}
public bool MoveNext()
{
this.index++;
return this.pool != null && this.pool.Count >= this.index;
}
public void Reset()
{
this.index = 0;
}
}
Run Code Online (Sandbox Code Playgroud)
你可以简单地返回StructEnumerable<T>
如下:
public StructEnumerable<T> Items
{
get { return new StructEnumerable<T>(this.pool); }
}
Run Code Online (Sandbox Code Playgroud)
并且C#可以使用正常的foreach迭代:
foreach (var item in pool.Items)
{
Console.WriteLine(item);
}
Run Code Online (Sandbox Code Playgroud)
请注意,您不能LINQ超过该项目.你需要IEnumerable接口,这涉及拳击和垃圾收集.
祝好运.
更新:
请注意,当同时使用foreach
两个数组时,List<T>
不会生成任何垃圾.在foreach
数组上使用时,C#会将操作转换为for
语句,同时List<T>
已经实现了一个struct
枚举器,导致foreach
不产生垃圾.
由于XBox的XNA也可以在Compact Framework上工作(我怀疑你正在处理的是你给出的提示(1)),我们可以相信XNA开发人员可以在foreach创建垃圾的时候告诉我们.
引用最相关的观点(虽然整篇文章值得一读):
在对一个
Collection<T>
普查员进行预告时,将分配.在对大多数其他集合执行foreach时,包括数组,列表,队列,链接列表等:
- 如果明确使用集合,则不会分配枚举器.
- 如果它们被强制转换为接口,则将分配一个枚举器.
因此,如果_pool是a List
,数组或类似的并且可以负担得起,则可以直接返回该类型或将其IEnumerable<T>
转换为相应的类型以避免在foreach期间出现垃圾.
作为一些额外的阅读,Shawn Hargreaves可以提供一些有用的附加信息.
(1)每秒60次调用,紧凑型框架,不能归结为本机代码,在触发GC之前分配1MB.