为GC性能划分和征服大型对象

时间:2010-03-24 23:25:16

标签: .net performance garbage-collection

在我的工作中,我们正在讨论清理大量托管的~50-100MB内存的不同方法。桌面上有两种方法(阅读:两位资深开发者不能同意)并没有经验团队的其他成员不确定哪种方法更可取,性能或可维护性。

收集的数据是许多小项目,约30000,其中包含其他项目,所有对象都被管理。这些对象之间有很多引用,包括事件处理程序,但不包括外部对象。我们将这一大组对象和引用称为一个名为blob的实体。

方法#1:确保切断对blob中对象的所有引用,并让GC处理blob和所有连接。

方法#2:在这些对象上实现IDisposable,然后对这些对象调用dispose并设置对Nothing的引用并删除处理程序。

第二种方法背后的理论是,因为较长寿命的物体在GC中需要更长的时间来清理。因此,通过将大型物体切割成更小的一口大小,垃圾收集器将更快地处理它们,从而提高性能。

所以我认为基本的问题是:拆分大量互连对象是否优化垃圾收集数据,或者最好将它们保持在一起并依靠垃圾收集算法为您处理数据?

我觉得这是一个预优化的案例,但我不知道GC是否足以知道它有什么帮助或阻碍它。

编辑:要强调内存的“blob”不是单个大对象,而是分别分配许多小对象。

如果它有用,可以多一点背景。我们有'泄漏',因为对象没有得到GCed。这两种方法都解决了泄漏问题,但在这一点上,这是一个更合适的辩论。

7 个答案:

答案 0 :(得分:9)

第二种方法是错误的 - 它假定实现IDisposable会影响垃圾收集器。

不幸的是, IDisposable与垃圾收集无关。它纯粹是关于发布非托管资源。听起来你的第二个高级开发人员试图为自己的利益做一点“太聪明”。

第一种方法应该没问题。一旦停止引用“blob”,博客中的每个对象都将无根,并且应该被清除。这可能在您发布引用后的某个不确定时间发生(除非您明确告知GC要收集,我不建议这样做)。相互依赖性将为您正确处理。

假设从理论上讲,实现IDisposable和清理内部引用可以加快收集过程。如果有(小)净收益,处理所有数据所花费的时间很可能超过GC中的任何收益 - 而且它实际上超出了您的业务关注。

然而,我怀疑它实际上会减慢整个垃圾收集器的速度,而不是加快速度。将数据集分解为大量对象无助于GC运行得更快 - 它仍然需要跟踪实时引用,这在这种情况下没有什么不同。

答案 1 :(得分:3)

这两种方法都没有意义。 GC在检测循环引用或复杂对象图时没有问题。设置对null的引用没有意义。 IDisposable没有改善GC性能。

如果您解决问题的方法有任何优势,那么将事件设置为null。如果它们是“向后”实现的话,它们具有保持引用对象的诀窍。换句话说:保持事件的发起者活着并拆除其客户。然后必须明确地取消订阅。

但是试图猜测这是错误的开始。任何像样的内存分析器都会向你显示什么参考使图表保持活着。

答案 2 :(得分:2)

IDisposable接口与垃圾收集无关。

有些对象(如文件流)会占用宝贵的资源(因为进程的文件描述符限制通常远低于现代操作系统的内存限制)。但是,垃圾收集器不承认它们;因此,如果您的文件描述符不足但仍有足够的内存,垃圾收集器可能无法运行。

IDisposable接口设置了一种机制,通过该机制,您可以放心,一旦对象实际变得无用,并且不仅在垃圾收集器决定运行时,将释放与托管对象关联的所有非托管资源。 / p>

因此,使对象IDisposable不会影响对象的垃圾收集方式。即使使用Dispose方法清除所有引用,对垃圾收集器运行几乎没有影响;只需清除对blob的引用,就可以让所有较小的对象一次性无根。

答案 3 :(得分:1)

如果你想要保存非托管资源(文件句柄,GDI句柄等)的对象的性能,那么微软暗示Dispose比Finalize更快。我不认为这是你想要实现的目标(你没有说过任何关于非托管资源的内容)。

让GC做它的事情(当我输入这个时,会出现另外两个答案,说同样的话,非常多)。

答案 4 :(得分:1)

答案 5 :(得分:1)

  

方法#2:实施IDisposable   这些对象然后调用dispose   这些对象和设置引用   什么都没有,删除处理程序。

...

  

第二种方法背后的理论   是因为大的长寿   对象需要更长的时间来清理   GC。所以,通过切割大型物体   进入较小的一口大小   垃圾收集器将处理它们   更快,因此性能提升。

我认为这不是真的;垃圾收集器的成本通常取决于活动对象及其引用的数量,以及死对象的数量(取决于GC的类型)。一旦您不需要一个对象(或多个对象)并从根对象切割引用路径到它/它们,“垃圾”对象之间的引用数量无关紧要。所以,我会说,只要确保不会有来自“blob”之外的悬空引用,你就会好的。

答案 6 :(得分:0)

任何具有终结器的物体都应该在被遗弃之前尽可能地处置掉。从GC性能角度来看,使用终结器放弃对象应被视为最坏的情况。

除此之外,即使在没有终结器的情况下,也可以构建这样的场景,其中最好是简单地将一个大blob从世界其他地方分离并让它死掉,并且可以构建其中更好的场景。打破大对象。一般来说,简单地让大物体死亡是最佳的,除了两个警告,这可能有利于打破它:

  1. 如果blob的某些部分是长寿命的,那么在下一次2级垃圾收集之前,无论最近如何分配,它都不可能被收集。相比之下,如果将所有包含blob的引用分开,则最近分配的部分可能有资格获得0级或1级集合。如果blob中的足够对象相对较新,则杀死较长寿命部分所持有的引用所需的工作量可能小于GC为保持较新对象所需的工作,直到下一个2级集合为止。 。
  2. 如果blob的一部分存在杂散引用并且blob保持不变,则该引用可以使整个blob保持活动状态。相比之下,如果斑点被吹散,则杂散参考可能仅保留其中的一小部分。这可能是好事也可能是坏事。如果替代方案是保持整个blob存活,那么只保留一小部分可能更好。另一方面,如果选择在发现问题和修复问题(消除杂散参考)与未发现问题之间,前者可能会更好。

我个人不喜欢在任何外部对象可能持有对发布者的引用的情况下放弃事件。主动清理似乎是一种更好的习惯。