首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么在Roslyn中有这么多对象池的实现?

为什么在Roslyn中有这么多对象池的实现?
EN

Stack Overflow用户
提问于 2015-06-03 10:55:00
回答 1查看 7.3K关注 0票数 38

ObjectPool是罗斯林C#编译器中的一种类型,用于重用经常使用的对象,这通常会得到新的启动和垃圾收集。这减少了必须发生的垃圾收集操作的数量和大小。

Roslyn编译器似乎有几个单独的对象池,而且每个池都有不同的大小。我想知道为什么会有这么多的实现,首选的实现是什么,以及为什么他们选择了20、100或128的池大小。

1- SharedPools -存储一个由20个对象组成的池,如果使用BigDefault,则存储100个对象。这个例子也很奇怪,因为它创建了一个新的PooledObject实例,当我们试图汇集对象而不是创建和销毁新的对象时,这是没有意义的。

代码语言:javascript
复制
// 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- ListPoolStringBuilderPool --不是严格分开的实现,而是围绕上面所示的特定于List和StringBuilder的SharedPools实现的包装器。因此,这再次使用了存储在SharedPools中的对象池。

代码语言:javascript
复制
// 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- PooledDictionaryPooledHashSet --它们直接使用ObjectPool,并且有一个完全独立的对象池。存储由128个对象组成的池。

代码语言:javascript
复制
// 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#对象池模式实现问题,请看我的答案。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-06-09 15:20:13

我是罗斯林表现队的领头羊。所有对象池的设计都是为了降低分配率,从而降低垃圾回收的频率。这是以增加长寿命(第2代)对象为代价的。这略微有助于编译器吞吐量,但主要影响是使用VB或C# IntelliSense时Visual的响应能力。

为什么有这么多的实现“。

没有快速的答案,但我能想到三个原因:

  1. 每个实现的目的略有不同,并为此目的进行了调整。
  2. “分层”-所有池都是内部的,编译器层的内部细节不能从工作区层引用,反之亦然。我们确实有一些代码共享通过链接的文件,但我们试图将它保持在最低限度。
  3. 在统一您今天看到的实现方面没有付出很大的努力。

首选的实现是什么?

ObjectPool<T>是首选的实现,也是大多数代码所使用的。请注意,ObjectPool<T>是由ArrayBuilder<T>.GetInstance()使用的,这可能是Roslyn中最大的池对象用户。由于ObjectPool<T>被大量使用,这是我们通过链接文件跨层重复代码的情况之一。对ObjectPool<T>进行了优化,以获得最大吞吐量。

在工作区层,您将看到SharedPool<T>试图在不相交的组件之间共享池实例,以减少总体内存使用。我们试图避免让每个组件创建自己的专用于特定用途的池,而是根据元素的类型进行共享。StringBuilderPool就是一个很好的例子。

为什么他们选择了一个20,100或128的游泳池。

通常,这是典型工作负载下的分析和测试结果。我们通常必须在分配率(池中的“错过”)和池中的总活字节之间取得平衡。其中的两个因素是:

  1. 最大并行度(访问池的并发线程)
  2. 访问模式包括重叠分配和嵌套分配。

在总体方案中,与编译的活动内存(第2代堆的大小)相比,池中的对象所持有的内存非常小,但是,我们也要注意不要将大型对象(通常是大型集合)返回池--我们只需调用ForgetTrackedObject就可以将它们丢弃在地板上。

对于未来,我认为我们可以改进的一个领域是具有受限长度的字节数组(缓冲区)池。这将特别有助于编译器发出阶段( MemoryStream )中的PEWriter实现。这些MemoryStreams需要连续字节数组来快速写入,但它们是动态大小的。这意味着它们偶尔需要调整尺寸--通常每次都要加倍。每个调整大小都是一个新的分配,但是能够从专用池中获取一个调整大小的缓冲区并将较小的缓冲区返回到不同的池将是很好的。例如,您将有一个用于64字节缓冲区的池,另一个用于128字节缓冲区的池等等。整个池内存将受到限制,但您可以避免随着缓冲区的增长而“搅动”GC堆。

再次感谢你的提问。

保罗·哈林顿。

票数 48
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30618067

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档