ObjectPool是罗斯林C#编译器中的一种类型,用于重用经常使用的对象,这通常会得到新的启动和垃圾收集。这减少了必须发生的垃圾收集操作的数量和大小。
Roslyn编译器似乎有几个单独的对象池,而且每个池都有不同的大小。我想知道为什么会有这么多的实现,首选的实现是什么,以及为什么他们选择了20、100或128的池大小。
1- SharedPools -存储一个由20个对象组成的池,如果使用BigDefault,则存储100个对象。这个例子也很奇怪,因为它创建了一个新的PooledObject实例,当我们试图汇集对象而不是创建和销毁新的对象时,这是没有意义的。
// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
// Do something with pooledObject.Object
}
// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);
// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][3] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
// Do something with list
}
finally
{
SharedPools.Default<List<Foo>>().Free(list);
}2- ListPool和StringBuilderPool --不是严格分开的实现,而是围绕上面所示的特定于List和StringBuilder的SharedPools实现的包装器。因此,这再次使用了存储在SharedPools中的对象池。
// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);
// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
// Do something with stringBuilder
}
finally
{
StringBuilderPool.Free(stringBuilder);
}3- PooledDictionary和PooledHashSet --它们直接使用ObjectPool,并且有一个完全独立的对象池。存储由128个对象组成的池。
// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();
// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
// Do something with hashSet.
}
finally
{
hashSet.Free();
}更新
在.NET核心中有新的对象池实现。关于C#对象池模式实现问题,请看我的答案。
发布于 2015-06-09 15:20:13
我是罗斯林表现队的领头羊。所有对象池的设计都是为了降低分配率,从而降低垃圾回收的频率。这是以增加长寿命(第2代)对象为代价的。这略微有助于编译器吞吐量,但主要影响是使用VB或C# IntelliSense时Visual的响应能力。
为什么有这么多的实现“。
没有快速的答案,但我能想到三个原因:
首选的实现是什么?
ObjectPool<T>是首选的实现,也是大多数代码所使用的。请注意,ObjectPool<T>是由ArrayBuilder<T>.GetInstance()使用的,这可能是Roslyn中最大的池对象用户。由于ObjectPool<T>被大量使用,这是我们通过链接文件跨层重复代码的情况之一。对ObjectPool<T>进行了优化,以获得最大吞吐量。
在工作区层,您将看到SharedPool<T>试图在不相交的组件之间共享池实例,以减少总体内存使用。我们试图避免让每个组件创建自己的专用于特定用途的池,而是根据元素的类型进行共享。StringBuilderPool就是一个很好的例子。
为什么他们选择了一个20,100或128的游泳池。
通常,这是典型工作负载下的分析和测试结果。我们通常必须在分配率(池中的“错过”)和池中的总活字节之间取得平衡。其中的两个因素是:
在总体方案中,与编译的活动内存(第2代堆的大小)相比,池中的对象所持有的内存非常小,但是,我们也要注意不要将大型对象(通常是大型集合)返回池--我们只需调用ForgetTrackedObject就可以将它们丢弃在地板上。
对于未来,我认为我们可以改进的一个领域是具有受限长度的字节数组(缓冲区)池。这将特别有助于编译器发出阶段( MemoryStream )中的PEWriter实现。这些MemoryStreams需要连续字节数组来快速写入,但它们是动态大小的。这意味着它们偶尔需要调整尺寸--通常每次都要加倍。每个调整大小都是一个新的分配,但是能够从专用池中获取一个调整大小的缓冲区并将较小的缓冲区返回到不同的池将是很好的。例如,您将有一个用于64字节缓冲区的池,另一个用于128字节缓冲区的池等等。整个池内存将受到限制,但您可以避免随着缓冲区的增长而“搅动”GC堆。
再次感谢你的提问。
保罗·哈林顿。
https://stackoverflow.com/questions/30618067
复制相似问题