【问题标题】:Divide and conquer of large objects for GC performance分而治之大对象的 GC 性能
【发布时间】:2010-03-24 23:25:16
【问题描述】:

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

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

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

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

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

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

我觉得这是一个预优化的情况,但我对 GC 的了解还不够,无法知道什么有助于或阻碍它。

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

如果有帮助,请提供更多背景信息。我们有“泄漏”,因为对象没有被 GCed。两种方法都解决了泄漏问题,但目前尚在争论哪种方法更合适。

【问题讨论】:

  • 你的“大物体”有多大?大到足以让大对象堆成为这里的一个因素吗?还是您的意思是“大”,因为只是许多小物体的集合?如今,100MB 的内存似乎并不“大”,到目前为止,开发团队花在讨论这个问题上的时间,你可以获得多少内存条?
  • 'large' 作为小对象的聚合,约 50MB 用于 30000 个对象。是的 100 或 50MB 不是很多,但它只是程序的一部分,应用程序的其余部分大约需要 750MB。
  • 为什么大对象比小对象更昂贵? GC主要受访问对象数量的影响。它们的大小基本上是无关紧要的
  • @jalf:实际上,没有。较大的对象不像小对象那样经常被收集。请参阅下面的@vittore 链接。
  • @zneak:这和我说的有什么矛盾?我说过收集对象的成本不受大小的影响(这在技术上是不准确的,因为大对象往往有更多的传出引用需要 GC 扫描,但如果将对象拆分为更小的子对象,也会产生相同的成本) .但我从来没有说过收集大对象的经常

标签: .net performance garbage-collection


【解决方案1】:

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

很遗憾,IDisposable 与垃圾回收无关。它纯粹是关于释放非托管资源。听起来你的第二个高级开发人员为了他们自己的利益而试图变得有点“太聪明”了。

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

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

但是,我怀疑它实际上会减慢垃圾收集器的整体速度,而不是加快速度。将数据集分解为许多对象不会帮助 GC 运行得更快——它仍然必须跟踪实时引用,在这种情况下没有什么不同。

【讨论】:

  • 你说的最后一点是最重要的——GC成本受存活对象的数量影响最大,其次是存活对象的大小。清理引用实际上只会使对象的存活时间超过必要的时间。
  • 换句话说,第二种方法行不通。第一种方法将;您可以在删除对 blob 的所有引用后手动调用垃圾收集器。
  • 是的 - 关键是它是 LIVE 对象的数量 - 调整“死”对象不会有太大变化。
  • 引用的数量会影响性能吗?还是只是对象的数量?我想我理解,垃圾收集器不关心集体大小,而是关心 blob 中有多少对象。因此,尝试在 blob 中分离对彼此的引用并不能真正将对象分解为更小的部分,至少根据 GC 不是。
  • ROOTED 对象使用的引用总数会影响整体性能。 GC - 但是无根的引用(甚至对象)的数量确实没有影响。
【解决方案2】:

这两种方法都没有意义。 GC 在检测循环引用或复杂对象图方面没有任何问题。将引用设置为 null 毫无意义。 IDisposable 对提高 GC 性能没有任何帮助。

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

但试图猜测这是错误的开始方法。任何体面的内存分析器都会向您展示使图表保持活力的参考。

【讨论】:

    【解决方案3】:

    IDisposable 接口与垃圾回收无关。

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

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

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

    【讨论】:

      【解决方案4】:

      Microsoft 暗示,如果您想要拥有非托管资源(文件句柄、GDI 句柄等)的对象的性能,则 Dispose 比 Finalize 更快。我不认为那是你想要达到的目标(你没有说任何关于非托管资源的事情)。

      让 GC 做它的事(当我输入这个时,出现了另外两个答案,说的差不多)。

      【讨论】:

      • 早些时候,是的。但在某些情况下也更快。这是来自微软网站的报价。 “在某些情况下,您可能希望为使用对象的程序员提供在垃圾收集器释放对象之前显式释放这些外部资源的能力。如果外部资源稀缺或昂贵,如果程序员显式释放可以实现更好的性能不再使用的资源。”
      【解决方案5】:
      【解决方案6】:

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

      ...

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

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

      【讨论】:

      • .NET 中的 GC 性能不取决于无法访问的对象的数量或大小除了 1) 通常,创建的对象越多,GC 必须越频繁运行和 2) 带有自定义终结器 的未引用对象必须明确处理。
      • @280Z28:是的,我考虑过一般的垃圾收集器,在这种情况下,是标记和清除的扫描阶段。
      • @280Z28:“.NET 中的 GC 性能不依赖于不可访问对象的数量或大小”。从 gen2 中清除更多对象将需要更长的时间。
      【解决方案7】:

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

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

      1. 如果 blob 的某些部分是长期存在的,那么它的任何部分(无论最近分配的时间如何)都不太可能在下一次 2 级垃圾回收之前被回收。相比之下,如果将所有将 blob 保持在一起的引用分开,则最近分配的部分可能有资格获得 level-0 或 level-1 集合。如果 blob 中有足够多的对象是相对较新的,那么杀死较长生命周期部分所持有的引用所需的工作可能会少于 GC 为保留较新的对象直到下一个 2 级收集所做的工作.
      2. 如果存在对部分 blob 的杂散引用并且 blob 保持不变,则该引用可能会使整个 blob 保持活动状态。相比之下,如果 blob 被炸开,则杂散参考可能只能保留其中的一小部分。这可能是好事也可能是坏事。如果替代方案是让整个 blob 保持活力,那么只保留一小部分可能会更好。另一方面,如果要在发现问题和解决问题(消除杂散参考)与不发现问题之间进行选择,前者可能会更好。

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

      【讨论】:

        猜你喜欢
        • 2016-04-04
        • 2013-11-09
        • 2020-07-12
        • 2013-01-16
        • 2013-01-31
        • 2017-06-07
        • 1970-01-01
        • 2012-03-04
        • 2012-01-29
        相关资源
        最近更新 更多