ChatGPT解决这个技术问题 Extra ChatGPT

为什么选择大对象堆,我们为什么要关心?

我已阅读有关世代和大型对象堆的信息。但是我仍然不明白拥有大型对象堆的意义(或好处)是什么?

如果 CLR 只是依赖第 2 代(考虑到 Gen0 和 Gen1 的阈值很小,无法处理大对象)来存储大对象,那么会出现什么问题(在性能或内存方面)?

这给了 .NET 设计者两个问题: 1. 为什么在抛出 OutOfMemoryException 之前不调用 LOH 碎片整理? 2. 为什么不让 LOH 对象有亲和性在一起(大的喜欢堆的末尾,小的喜欢堆的开头)

H
Hans Passant

垃圾回收不仅会清除未引用的对象,还会压缩堆。这是一个非常重要的优化。它不仅使内存使用效率更高(没有未使用的漏洞),而且使 CPU 缓存更高效。缓存在现代处理器上非常重要,它们比内存总线快一个数量级。

压缩只是通过复制字节来完成。然而,这需要时间。对象越大,复制它的成本就越有可能超过可能的 CPU 缓存使用改进。

因此,他们运行了一系列基准测试来确定盈亏平衡点。并达到 85,000 字节作为复制不再提高性能的截止点。除了 double 数组的特殊例外,当数组有超过 1000 个元素时,它们被认为是“大”的。这是对 32 位代码的另一种优化,大对象堆分配器具有特殊属性,它在对齐到 8 的地址分配内存,这与仅分配对齐到 4 的常规分代分配器不同。这种对齐对于 double 来说很重要,读取或写入未对齐的双精度非常昂贵。奇怪的是,稀疏的微软信息从未提到过长数组,不知道这是怎么回事。

Fwiw,有很多程序员担心大型对象堆没有被压缩。当他们编写的程序消耗了整个可用地址空间的一半以上时,这总是会被触发。随后使用诸如内存分析器之类的工具来找出程序被炸毁的原因,即使仍有大量未使用的虚拟内存可用。这样的工具显示了 LOH 中的漏洞,未使用的内存块,以前有一个大对象存在,但被垃圾收集了。这就是 LOH 不可避免的代价,这个洞只能通过分配给大小相等或更小的对象来重复使用。真正的问题是假设应该允许程序随时消耗所有虚拟内存。

仅通过在 64 位操作系统上运行代码即可完全消失的问题。 64 位进程有 8 TB 的可用虚拟内存地址空间,比 32 位进程多 3 个数量级。你只是不能用完孔。

长话短说,LOH 使代码运行更高效。代价是使用可用的虚拟内存地址空间效率较低。

更新,.NET 4.5.1 现在支持压缩 LOH, GCSettings.LargeObjectHeapCompactionMode 属性。请注意后果。


@Hans Passant,您能否澄清一下x64系统,您的意思是这个问题完全消失了?
当然,碎片问题在 x64 上也是一样的。在启动之前,只需要再运行几天即可运行您的服务器进程。
嗯,不,永远不要低估 3 个数量级。垃圾收集一个 4 TB 的堆需要多长时间,这是你无法避免在接近它之前很久才发现的。
@HansPassant 请您详细说明此声明:“垃圾收集 4 TB 堆需要多长时间是您无法避免在接近它之前发现的。”
@HansPassant 当来自 SOH 的对象变为(增长)超过 85kB(85kb)时会发生什么,它移动到 LOH 或它仍然在 SOH 中?
o
oleksii

如果对象的大小大于某个固定值(.NET 1 中为 85000 字节),则 CLR 将其放入大对象堆中。这优化了:

对象分配(小对象不与大对象混合) 垃圾收集(LOH 仅在完全 GC 时收集) 内存碎片整理(LOH 很少被压缩)


P
Pavel Chuchuva

小对象堆 (SOH) 和大对象堆 (LOH) 的本质区别在于,SOH 中的内存在收集时会被压缩,而 LOH 不会,如 this article 所示。压缩大型对象的成本很高。与文章中的示例类似,假设在内存中移动一个字节需要 2 个周期,那么在 2GHz 的计算机中压缩一个 8MB 的对象需要 8ms,这是一个很大的成本。考虑到大对象(大多数情况下是数组)在实践中很常见,我想这就是微软将大对象固定在内存中并提出 LOH 的原因。

顺便说一句,根据this post,LOH 通常不会产生内存碎片问题。


将大量数据加载到托管对象中通常会使压缩 LOH 的 8 毫秒成本相形见绌。在大多数大数据应用程序的实践中,LOH 成本与其他应用程序性能相比微不足道。
M
Myles McDonnell

原则是,一个进程不太可能(并且很可能是糟糕的设计)会创建大量寿命短的大对象,因此 CLR 将大对象分配给一个单独的堆,在该堆上它按照与常规堆不同的计划运行 GC。 http://msdn.microsoft.com/en-us/magazine/cc534993.aspx


同样,将大对象放在第 2 代可能会损害性能,因为压缩内存需要很长时间,尤其是在释放少量内存并且必须将大对象复制到新位置的情况下。出于性能原因,当前 LOH 未压缩。
我认为这只是糟糕的设计,因为 GC 不能很好地处理它。
@CodeInChaos 显然,有一些improvements coming in .NET 4.5
@CodeInChaos:虽然系统在尝试从即使是短暂的 LOH 对象中回收内存之前等到 gen2 集合可能是有意义的,但我看不到声明 LOH 对象(以及它们持有的任何对象)的任何性能优势参考)在 gen0 和 gen1 收集期间无条件地存在。这种假设是否可以进行一些优化?
@supercat 我查看了 Myles McDonnell 提到的链接。我的理解是: 1. LOH 收集发生在第 2 代 GC 中。 2. LOH 集合不包括压缩(在撰写本文时)。相反,它会将死对象标记为可重用,如果足够大,这些漏洞将服务于未来的 LOH 分配。由于第 1 点,考虑到如果第 2 代中有很多对象,第 2 代 GC 会很慢,我认为在这种情况下最好尽可能避免使用 LOH。
C
Chris Shain

我不是 CLR 方面的专家,但我想为大型对象设置一个专用堆可以防止对现有分代堆进行不必要的 GC 扫描。分配大对象需要大量连续的可用内存。为了从世代堆中分散的“洞”中提供这一点,您需要频繁的压缩(仅在 GC 周期中完成)。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅