【问题标题】:who profits more from pooling? managed / unmanaged?谁从池中获利更多?托管/非托管?
【发布时间】:2011-01-17 11:21:39
【问题描述】:

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

部分但不是所有的对象可以重复使用。因此,考虑使用池以提高执行速度并最大限度地降低内存碎片的风险。

您希望哪个应用程序从池中获得更多收益?为什么?

@Update:这是关于一个数学库的。因此,那些大对象将是值类型的数组。大多数情况下,对于 LOH 来说足够大。我很肯定,池化会大大提高托管方的性能。存在许多库 - 用于托管/非托管环境。我所知道的他们中没有一个人真正做到了这种汇集。我想知道为什么?

【问题讨论】:

  • 我希望是这样。但它只是一个我们无法摆脱的讨论话题......我不想在这里对可能的答案产生偏见,但我当然有一个想法,我希望得到证实或反驳。还应该有助于了解上下文。
  • Afaik 池对于大型托管对象非常重要。

标签: .net performance unmanaged pooling


【解决方案1】:

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

如果是较小对象的图,则将它们存储在 SOH(小对象堆)中。在这种情况下,如果您正在创建对象并立即让它们离开,您将从垃圾收集器的优化中获得最大的好处,该优化假定一个代模型。我的意思是,您要么创建对象并让它们消亡,要么永远保留它们。只持有它们“一段时间”,或者换句话说,池化只会让它们升级到更高的代(直到第 2 代),这会降低 GC 的性能,因为清理第 2 代对象的成本很高(然而,第 2 代中的永恒物品并不昂贵)。不用担心内存碎片。除非您正在执行互操作或固定对象之类的花哨的事情,否则 GC 在避免内存碎片方面非常有效 - 它会压缩从临时段中释放的内存。

如果您确实有非常大的对象(例如,非常大的数组),那么将它们池化是值得的。但是请注意,如果数组包含对较小对象的引用,则将它们池化会导致我在上一段中谈到的问题,因此您应该小心频繁地清理数组(使其引用指向 null)(每次迭代? )。

话虽如此,您调用 IDisposable 的事实并不是清理对象。 GC 会这样做。 Dispose 负责清理非托管资源。尽管如此,非常重要的是,您继续对每个类实现 IDisposable 的对象调用 Dispose(最好的方法是通过 finally),因为您可能会立即释放非托管资源,并且还因为您告诉 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

【讨论】:

  • 谢谢。我已经知道那些概念。在您的帖子中,只有一个想法我没有遵循:确保调用 Dispose() 的更好方法是在 using() 块中使用该对象。有问题的应用程序分配了很多“非常大”的数组。不幸的是,这并不能回答这个问题。您仅指托管方面。与非托管方相比如何?
【解决方案2】:

感觉很奇怪,但我会尝试自己回答。我可以在上面买一些 cmets:

如果以繁重的方式(循环)分配和释放非常大的对象,两个平台都会出现碎片。对于非托管应用程序,直接从虚拟地址空间进行分配。通常工作数组将被包装在一个类 (c++) 中,为漂亮的短语法提供运算符重载、一些引用处理和一个析构函数,确保在超出范围时立即释放数组.然而,请求的数组并非始终具有相同的大小 - 如果请求更大的数组,则无法重用相同的地址块,这可能会随着时间的推移导致碎片。此外,没有办法找到 块,该块正好满足请求的数组长度。操作系统将简单地使用足够大的第一个块 - 即使它根据需要更大,并且可能更好地满足对稍后即将推出的更大阵列的请求。汇集如何改善这种情况?

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

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

但由于 LOH 段是从虚拟地址空间分配的,因此这里的碎片也是一个问题 - 就像非托管应用程序一样。事实上,这两个应用程序的分配模式对于操作系统的内存管理器来说应该非常相似。 (?) 有一个区别:GC 同时释放了所有数组。但是,“非常大的数组”会对GC产生很大的压力。在收集发生之前,只能同时保存相对较少数量的“非常大的数组”。基本上,应用程序通常会在 GC 中花费合理的时间(大约 5..45%),这也是因为几乎所有的回收都是昂贵的 Gen2 回收,并且几乎每次分配都会导致这样的 Gen 2 回收。

这里的池化可能会有很大帮助。一旦阵列没有被释放到操作系统而是被收集到池中,它们立即可用于进一步的请求。 (这是 IDisposable 不仅适用于非托管资源的原因之一)。框架/库只需要确保足够早地将数组放入池中,并允许在实际需要较小尺寸的情况下重用较大的数组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-06
    • 2011-10-01
    • 2012-01-22
    • 1970-01-01
    • 1970-01-01
    • 2018-03-19
    • 2013-01-07
    相关资源
    最近更新 更多