大阵列和LOH碎片。什么是公认的惯例?

问题描述 投票:12回答:5

我还有另一个活跃的问题HERE关于一些可能涉及LOH碎片的可能其他未知数的无望记忆问题。

我现在的问题是,接受的做事方式是什么?如果我的应用程序需要在Visual C#中完成,并且需要处理大型数组到int [4000000]的调整,我怎么能不注意垃圾收集器拒绝处理LOH?

似乎我被迫将任何大型数组全局化,并且从不在它们周围使用“new”这个词。所以,我留下了带有“maxindex”变量的不合适的全局数组,而不是由函数传递的整齐大小的数组。

我总是被告知这是不好的做法。还有什么选择?

System.GC.CollectLOH("Seriously")的曲调是否有某种功能?是否有可能将垃圾收集外包给System.GC以外的其他方式?

无论如何,处理大型(> 85Kb)变量的普遍接受的规则是什么?

c# arrays large-object-heap
5个回答
26
投票

首先,垃圾收集器确实收集了LOH,因此不要立即被它的早期吓到。收集第2代时收集LOH。

区别在于LOH没有被压缩,这意味着如果你有一个具有较长寿命的物体,那么你将有效地将LOH分成两个部分 - 前面的区域和该物体之后的区域。如果这种情况继续发生,那么你可能会遇到这样的情况,即长期存在的对象之间的空间不足以进行后续分配,并且.NET必须分配越来越多的内存才能放置大对象,即LOH变得支离破碎。

现在,话虽如此,如果LOH的末端区域完全没有活物,那么LOH的尺寸会缩小,所以唯一的问题是如果你将物体留在那里很长时间(例如应用的持续时间)。

从.NET 4.5.1开始,可以压缩LOH,请参阅GCSettings.LargeObjectHeapCompactionMode属性。

避免LOH碎片的策略是:

  • 避免创建悬挂的大型对象。基本上这只是意味着大型数组或包装大型数组的对象(例如包装字节数组的MemoryStream),因为没有其他东西那么大(复杂对象的组件分别存储在堆上,所以很少很大)。还要注意大词典和列表,因为它们在内部使用数组。
  • 注意双阵列 - 这些进入LOH的门槛要小得多,要小得多 - 我不记得确切的数字,但只有几千。
  • 如果你需要一个MemoryStream,考虑制作一个支持许多较小阵列而不是一个大阵列的分块版本。您还可以使用分块来制作IList和IDictionary的自定义版本,以避免首先出现在LOH中的内容。
  • 避免很长的Remoting调用,因为Remoting大量使用MemoryStreams,它可以在调用期间分割LOH。
  • 注意字符串实习 - 由于某些原因,它们作为页面存储在LOH上,如果你的应用程序继续遇到新的字符串到实习生,可能会造成严重的碎片,即避免使用string.Intern,除非已知字符串集是有限的在应用程序的生命早期遇到全套。 (见my earlier question。)
  • 使用罢工之子来查看使用LOH内存究竟是什么。再次查看this question有关如何执行此操作的详细信息。
  • 考虑一下pooling large arrays

编辑:双阵列的LOH阈值似乎是8k。


8
投票

这是一个老问题,但我认为用.NET中引入的更改更新答案并没有什么坏处。现在可以对大对象堆进行碎片整理。显然,首选应该是确保做出最佳设计选择,但现在选择这个选项真是太好了。

https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx

“从.NET Framework 4.5.1开始,您可以通过在调用Collect方法之前将GCSettings.LargeObjectHeapCompactionMode属性设置为GCLargeObjectHeapCompactionMode.CompactOnce来压缩大对象堆(LOH),如以下示例所示。”

可以在System.Runtime命名空间中找到GCSettings

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(); 

7
投票

首先要想到的是将阵列分成较小的阵列,这样它们就无法达到GC所需的内存来放入LOH。你可以将数组吐成10,000个较小的数组,然后构建一个对象,根据你传递的索引器知道要查找哪个数组。

现在我还没有看到代码,但我也会质疑为什么你需要一个大的数组。我可能会考虑重构代码,因此所有这些信息不需要立即存储在内存中。


6
投票

你弄错了。您不需要具有4000000的数组大小,并且您绝对不需要调用垃圾收集器。

  • 编写自己的IList实现。喜欢“PagedList”
  • 将项目存储在65536个元素的数组中。
  • 创建一个数组数组来保存页面。

这允许您仅使用一个重定向访问基本上所有元素。并且,由于单个阵列较小,碎片不是问题...

......如果是......那么REUSE页面。不要把它们扔掉处理掉,把它们放在一个静态的“PageList”上并先从那里拉出来。所有这些都可以在你的课堂上透明地完成。

真正的好处是这个List在内存使用方面非常动态。您可能想要调整holder数组(重定向器)的大小。即使没有,也只是每页约512kbdata。

第二级数组基本上每字节64k - 一个类为8字节(每页512kb,32位256kb),或每个结构字节64kb。

技术上:

将int []转换为int [] []

根据需要确定32位或64位是否更好;)两者都有优点和缺点。

处理像这样的大型数组在任何语言中都是笨重的 - 如果必须的话,那么......基本上....在程序启动时分配并且永远不会重新创建。只有解决方案


0
投票

在问题如何产生方面,我在上面的答案中加入了详细说明。 LOH的碎片不仅取决于长寿命的对象,而且如果你有多个线程并且每个都创建大型列表进入LOH的情况,那么你可能会遇到第一个线程的情况需要增加它的List但是下一个连续的内存位已经被第二个线程中的List占用,因此运行时将为第一个线程List分配新的内存 - 留下一个相当大的漏洞。这是目前在我继承的一个项目中发生的事情,因此即使LOH大约为4.5 MB,运行时总共有117 MB可用内存,但最大可用内存段是28MB。

没有多个线程可能会发生这种情况的另一种方法是,如果你在某种循环中添加了多个列表,并且每个列表都扩展到最初分配给它的内存之外,那么当它们超出其分配的空间时,每个都会超越另一个。

一个有用的链接是:https://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

仍在为此寻找解决方案,一种选择可能是使用某种池化对象并在进行工作时从池中请求。如果您正在处理大型阵列,那么另一种选择是开发自定义集合,例如集合的集合,这样你就不会只有一个巨大的列表,而是将它分解成更小的列表,每个列表都避免使用LOH。

© www.soinside.com 2019 - 2024. All rights reserved.