1

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

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

您希望哪个应用程序从池中获利更多,为什么?

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

4

2 回答 2

3

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

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

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

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

归根结底,GC 在分配和清理东西方面非常出色。试图帮助它通常会导致性能下降,除非你真的知道发生了什么。

要真正理解我在说什么:

垃圾收集:Microsoft .NET Framework 中的自动内存管理

垃圾收集:Microsoft .NET Framework 2 中的自动内存管理

发现大对象堆

于 2011-01-17T13:06:02.537 回答
1

感觉很奇怪,但我会尝试自己回答。我可以得到一些评论:

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

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

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

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

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

于 2011-01-17T17:43:34.057 回答