.NET垃圾收集器的效率

12

好的,这是问题的要点。 有些人将他们的生命置于 .NET 的垃圾回收器之手,还有一些人则不信任它。

我是那些部分信任它的人之一,只要不是极其性能关键的情况(我知道,我知道... 性能关键 + .net 不是最佳组合),在这种情况下,我更喜欢手动处理我的对象和资源。

我的问题是,有没有关于垃圾回收器在性能方面的效率究竟是高效还是低效的事实?

请不要分享任何基于个人经验的主观意见或可能的假设,我想要客观的事实。我也不希望进行赞成/反对的讨论,因为这不会回答问题。

谢谢。

编辑: 澄清一下,我基本上的意思是:无论我们写什么样的应用程序,资源是否关键,我们都可以忘记一切,让 GC 处理它吗?还是不能?

我想得到一个关于 GC 真正做了什么、没做什么以及可能遇到失败的地方,手动内存管理会成功的场景,如果存在这样的情况,是否有限制?我不知道如何进一步解释我的问题。

我没有任何应用程序的问题,这是一个理论问题。


6
定义"efficient"。如果不知道您想如何衡量其效率,很难回答这个问题。 - Sam Holder
1
@Jonas:根据什么来比较呢?通常重要的不是分配/释放内存所花费的总时间,而是它们何时被分配/释放——现代垃圾收集器是并发的,这意味着它们可能会做更多的工作,但是在“空闲”时间内完成得更快,而且你甚至都没有注意到影响。这算更有效率还是更低效呢? - Aaronaught
以内存敏感型应用程序为例,其中大量且我的意思是“大量”的数据存储在内存中,并且需要以非常快的速度处理对象。垃圾回收机制是否足够高效? - Jonas B
除非你正在编写实时应用程序,否则我想不出任何情况下 .net 的垃圾回收器会成为问题,特别是在现代硬件上。除非你谈论的是嵌入式系统,在这种情况下,有许多应用程序它并不重要,也有许多应用程序它很重要...但你在这里谈论的是什么样的应用程序? - Erik Funkenbusch
@Jonas - 如果你的应用程序需要将大量数据加载到内存中,并且需要以非常快的速度处理它,我会质疑你的应用程序设计。往往情况下,你可以设计方法来最小化内存的释放,例如重用缓冲区和其他对象以及创建对象池。 - Erik Funkenbusch
显示剩余13条评论
5个回答

9
对于大多数应用程序来说,GC的效率已经足够高了。但是您不必害怕GC。在一些需要低延迟的热门系统上,您应该以完全避免GC的方式编程。我建议您查看这份Rapid Addition白皮书
尽管GC执行得很快,但是它确实需要时间来执行,因此在持续运行模式下进行垃圾回收可能会引入不良延迟和延迟变化,对那些对延迟非常敏感的应用程序造成影响。例如,如果您每秒处理100,000个消息,并且每个消息使用一个小的临时字符串(长度为2个字符,大约8个字节,这取决于字符串编码和字符串对象的实现),那么每秒就会分配近1MB的垃圾。对于需要在16小时内保持恒定性能的系统而言,这意味着您需要清理16小时x 60分钟x 60秒x 1MB的内存,大约56 GB的内存。从垃圾收集器可以期望的最好结果是,它将在0代或1代收集中完全清理掉这些垃圾并导致抖动,最坏的结果是它将导致2代垃圾收集并伴随着更大的延迟峰值。
但请注意,像避免GC影响这样的技巧非常困难。您真的需要考虑您的性能要求是否到达需要考虑GC影响的程度。

那是一些有趣的阅读,回答了我很多问题,再次感谢。 - Jonas B

5

我可以告诉你一些我在.NET垃圾收集器方面遇到的问题。

如果你运行一个使用服务器GC的应用程序(例如ASP.NET应用程序),那么你的延迟将非常糟糕,当你的线程都无法取得任何进展时,会有大约一秒钟的暂停。这是因为.NET 4服务器GC是一种停止-世界GC。显然,.NET 4.5将引入微软的第一个基本上并发的服务器GC。

我曾经编写过一些仪器代码来测量使用内置集合(如ConcurrentBag)的并发系统中的延迟,并由于.NET GC不对大对象进行碎片整理而在32位内存中不断耗尽。我不得不用纯函数数据结构替换基于数组的数据结构,这些数据结构被分散成数百万个小块,以避免在大对象堆(LOH)上有任何导致碎片化的东西。

我发现了GC中的漏洞 this one,会导致GC泄露内存,直到所有系统内存耗尽,此时堆被清除,进行一次大型GC循环,暂停所有线程以及其他进程(因为系统已经开始交换),持续时间长达几分钟!
虽然最新的.NET GC有“低延迟”设置,但实际上它只是关闭垃圾收集器,因此您的程序会泄漏内存,直到出现一个巨大的GC暂停。Microsoft似乎更喜欢使用这样的解决方案,相当于说“如果您想要可用的延迟,请编写自己的垃圾收集器”。
然而,.NET垃圾回收器通常非常出色,当小心使用时,可以从中获得良好的结果。例如,最近我编写了一个容错服务器,平均延迟为114微秒,95%的延迟在0.5毫秒以下。考虑到我仅用几个月的时间独自用F#编写了整个平台,这与最先进技术(见此处此处)非常接近是令人印象深刻的。实际上,网络对延迟的影响比.NET垃圾回收器更大。

这是一些有趣的阅读材料,谢谢!我也会阅读你发布的链接 :) - Jonas B
2
我可以确认 SustainedLowLatency 实际上只是关闭 GC。当我遇到 OutOfMemoryException 时,这让我感到非常震惊,但总的来说,它表现得很好,因为我需要几乎零延迟,但每隔几分钟可以容忍暂停。然后我切换回 Concurrent 并让 GC 清理后再次关闭它。 - codekaizen

3

你不需要担心这个。

原因是,如果你发现垃圾回收(GC)正在占用大量时间的边缘情况,你将能够通过进行点优化来处理它。这不会是世界末日 - 这可能会非常容易。

而且你不太可能找到这样的边缘情况。它的性能真的非常出色。如果你只在典型的C和C ++实现中体验过堆分配器,那么.NET GC是完全不同的东西。我对它感到非常惊讶,我写了这篇博客文章试图传达这一点


2
无论您是否使用GC,都不能总是忘记内存分配。优秀的GC实现使您大部分时间不需要考虑内存分配。然而,并没有终极内存分配器。对于一些关键任务,您必须知道如何管理内存,这意味着了解内部工作方式。无论是GC还是手动堆分配,这都是真实的。
有一些GC提供实时保证。 "实时"并不意味着"快",它意味着分配器响应时间可以受到限制。这是嵌入式系统所需的保证,例如用于驾驶飞机中的电气命令。奇怪的是,与手动分配器相比,垃圾收集器更容易提供实时保证。
当前.NET实现中的GC并非实时,它们具有启发式高效和快速的特点。请注意,同样适用于C中的malloc()或C ++中的new的手动分配。因此,如果您需要实时保证,则已经需要使用某些特殊工具。如果您不需要,则我不想让您为我使用的汽车和飞机设计嵌入式电子设备!

1

任何垃圾回收算法都会偏向某些活动(例如:优化)。您将不得不根据您的使用模式测试GC,以查看它对您的效率如何。即使其他人研究了.NET GC的特定行为并产生了“事实”和“数字”,您的结果可能大相径庭。

我认为这个问题唯一合理的答案就是轶事。大多数人在大规模情况下甚至不会遇到GC效率问题。它被认为至少与其他托管语言的GC一样有效或更有效。如果您仍然担心,您可能不应该使用托管语言。


谢谢回复,这并不是一个问题,我只是认为这是你应该了解的一些知识,而我并不了解,所以才会问。 - Jonas B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接