【问题标题】:Why are there so many implementations of Object Pooling in Roslyn?为什么 Roslyn 中有这么多对象池的实现?
【发布时间】:2015-06-03 10:55:00
【问题描述】:

ObjectPool 是 Roslyn C# 编译器中使用的一种类型,用于重用经常使用的对象,这些对象通常会被更新并经常被垃圾收集。这减少了必须发生的垃圾收集操作的数量和大小。

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

1 - SharedPools - 如果使用 BigDefault,则存储 20 个对象或 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 - ListPoolStringBuilderPool - 不是严格分离的实现,而是围绕上面显示的专门针对 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 - PooledDictionaryPooledHashSet - 它们直接使用 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 Core 中有新的对象池实现。请参阅我对C# Object Pooling Pattern implementation 问题的回答。

【问题讨论】:

  • 考虑到微软一直反对 .NET 中对象池的概念,因为他们总是说 gen0 对象的 GC 非常快,这是一个有趣的转变 :-)
  • 编译器不是一个您不希望暂停的实时应用程序......而且它不像 SO,数百个用户将同时连接。他们想要优化它,因为他们不希望它比旧编译器慢,并且他们使用了对象池......但这并不意味着我需要喜欢他们所做的。
  • 我会做和他们一样的事情 :-) 而且我会同时感到聪明 肮脏 :-)
  • @xanatos 命令行编译器不关心暂停。 IDE 中内置的编译器可以。
  • 我想这最终是关于性能的,所以我相信答案是基本的,而不是你想听到的。当你真正优化东西时,你并不真正关心那里有什么。你只是希望它尽可能快。所以,你找到一个热点,想一个可能的方法来优化它,看看现有代码是否完全符合你的要求 - 如果它不是完全你想要的,你只需实现它.在 HELL(高效低级代码)的世界中,没有“适当的设计”之类的东西;一切都是允许的,最终目标是唯一重要的事情。

标签: c# .net garbage-collection roslyn


【解决方案1】:

我是 Roslyn Performance v-team 的负责人。所有对象池都旨在降低分配率,从而降低垃圾收集的频率。这是以添加长寿命(第 2 代)对象为代价的。这有助于稍微提高编译器的吞吐量,但主要影响的是使用 VB 或 C# IntelliSense 时 Visual Studio 的响应能力。

为什么会有这么多实现”。

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

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

首选的实现是什么

ObjectPool&lt;T&gt; 是首选实现,也是大多数代码使用的。请注意,ObjectPool&lt;T&gt;ArrayBuilder&lt;T&gt;.GetInstance() 使用,这可能是 Roslyn 中池对象的最大用户。因为ObjectPool&lt;T&gt; 被大量使用,这是我们通过链接文件跨层复制代码的情况之一。 ObjectPool&lt;T&gt; 已针对最大吞吐量进行了调整。

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

为什么他们选择 20、100 或 128 的池大小。

通常,这是在典型工作负载下进行分析和检测的结果。我们通常必须在分配率(池中的“未命中”)和池中的总有效字节之间取得平衡。发挥作用的两个因素是:

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

总体而言,池中对象持有的内存与编译时的总活动内存(第 2 代堆的大小)相比非常小,但我们也注意不要返回巨大的对象(通常是大型收藏品)回到游泳池 - 我们只需拨打电话ForgetTrackedObject 将它们放在地板上

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

再次感谢您的提问。

保罗·哈灵顿。

【讨论】:

  • 不,谢谢保罗的回答!这就是我喜欢 StackOverflow 的地方,问一个关于一些软件的问题,开发人员就会出现并为你回答。我正在为我的ASP.NET MVC Boilerplate 项目研究对象池。
  • 为什么我们不能摆脱这个 90 年代的遗物???现在它是开源的,我们应该做 Delphi 在移动设备上所做的事情,而 Objective-c 只是自动释放该特定对象。它会在适当的时候调用 Free()。
猜你喜欢
  • 1970-01-01
  • 2014-07-30
  • 2014-10-27
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多