谁从汇集中获利更多?托管/非托管?

时间:2011-01-17 11:21:39

标签: .net performance unmanaged pooling

有两个类似的应用程序:一个是托管的,另一个是非托管的。两者都利用大型对象的重分配模式。即他们在(长时间运行)循环中请求了很多这些对象,并在使用后立即释放这些对象。托管应用程序使用IDisposable()立即调用。未管理的是利用析构函数。

有些但不是所有对象都可以重复使用。因此,考虑池以提高执行速度并最小化内存碎片的风险。

您希望哪种应用程序可以从池中获得更多利润?为什么?

@Update:这是一个数学库。因此,那些大对象将是值类型的数组。大部分都足够LOH。我很积极,汇集会大大提高管理方面的绩效。存在许多库 - 用于托管/非托管环境。我所知道的都不是真的这样的汇集。我想知道为什么?

2 个答案:

答案 0 :(得分:3)

首先,稍微考虑一下大型物体是什么。在.net中,大对象被认为具有85,000或更多字节。你真的有这么大的物体,或者你有一个非常大的小物体图吗?

如果它是较小对象的图形,则它们存储在SOH(小对象堆)中。在这种情况下,如果您正在创建对象并让它们立即运行,那么您将从垃圾收集器的优化中获得最大的好处,这些优化假定为世代模型。我的意思是你要么创造物体,让它们去死,要么永远保持它们。只是“暂时”保持它们,或者换句话说,合并,只会让它们升级到更高代(直到第2代)并且会破坏GC的性能,因为清理第2代物体是昂贵的(然而,第2代的永恒物品并不昂贵。不要担心内存碎片。除非你正在做像固定对象这样的互操作或花哨的东西,否则GC在避免内存碎片方面非常有效 - 它可以压缩内存,从短暂的部分中解脱出来。

如果确实有非常大的对象(例如,非常大的数组),那么它可以支付它们。但请注意,如果数组包含对较小对象的引用,那么汇集它们会导致我在前一段中讨论过的问题,所以你应该小心清理数组(让它的引用指向null)经常(每次迭代? )。

话虽如此,你正在调用IDisposable的事实并不是清理对象。 GC这样做了。 Dispose负责清理非托管资源。尽管如此,非常重要的是,你继续在其类实现IDisposable的每个对象上调用Dispose(最好的方法是最终通过),因为你可能会立即释放非托管资源,也因为你告诉GC它没有不需要调用对象的终结器,这将导致对象的不必要的提升,正如我们所看到的那样,是不是没有。

总而言之,GC在分配和清理内容方面非常出色。试图帮助它通常会导致性能下降,除非你真的知道发生了什么。

要真正理解我在说什么:

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework 2

Large Object Heap Uncovered

答案 1 :(得分:1)

感觉奇怪,但我会尝试自己回答。我可以对此发表一些意见:

如果分配非常大的对象并以沉重的方式释放(循环),则两个平台都会受到碎片的影响。如果是非托管应用,则直接从虚拟地址空间进行分配。通常,工作数组将被包装在一个类(c ++)中,提供运算符重载以获得良好的短语法,一些引用处理和析构函数,这样可以确保在超出范围时立即释放数组 。尽管如此,请求的数组始终没有相同的大小 - 如果请求更大的数组,则相同的地址块无法重复使用,这可能会导致一段时间内出现碎片。此外,无法找到 块,它正好服务于所请求的数组长度。操作系统只会使用第一个块,这个块足够大 - 即使它根据需要更大,也可能更好地满足后来即将推出的更大阵列的请求。如何汇集改善这种情况?

可以想象,对较小的请求使用较大的数组。该类将处理从底层数组的真实长度到外部世界所需的虚拟长度的转换。该池可以帮助提供“第一个阵列,这是足够长的” - 与操作系统相比,操作系统总是给出确切的长度。这可能会限制碎片,因为在虚拟地址空间中创建的孔更少。另一方面,整体内存大小会增加。对于几乎随机的分配模式,池化会带来很少甚至没有利润,但我只想吃稀有的内存。

托管方面,情况更糟。首先,存在两个可能的碎片目标:虚拟地址空间托管大对象堆。在这种情况下,后者将更多地是从OS中单独分配的各个序列的集合。每个seqment主要用于单个数组(因为我们在这里谈论的是非常大的数组)。如果GC释放了一个数组,则整个段将返回到OS。所以碎片化不会成为LOH的问题(参考:我自己的想法和使用VMMap的一些经验观察,所以任何评论都非常欢迎!)。

但是由于LOH段是从虚拟地址空间分配的,因此碎片也是一个问题 - 就像非托管应用程序一样。实际上,对于OS的内存管理器,两个应用程序的分配模式应该看起来非常相似。 (?)有一个区别:GC同时释放数组。但是,“非常大的阵列”会对GC产生很大的压力。只有相对少量的“真正大型阵列”可以同时保持,直到收集发生。基本上,应用程序通常在GC中花费合理的时间(大约5..45%),也因为几乎所有的集合都将是昂贵的Gen2集合,并且几乎每个分配都将产生这样的Gen 2集合。

这里汇集可能会有很大帮助。一旦阵列没有被释放到操作系统而是收集在池中,它们立即可用于进一步的请求。 (这是一个原因,为什么IDisposable不仅仅用于非托管资源)。框架/库只需要确保,数组尽早放置在池中,并且可以在需要更小尺寸的情况下重用更大的数组。