【问题标题】:Should I call GC.Collect immediately after using the large object heap to prevent fragmentation我应该在使用大对象堆后立即调用 GC.Collect 以防止碎片吗
【发布时间】:2023-04-07 12:55:01
【问题描述】:

我的应用程序对大型对象进行了大量的二进制序列化和压缩。未压缩的序列化数据集约为 14 MB。压缩后约为 1.5 MB。我发现每当我在我的数据集上调用 serialize 方法时,我的大型对象堆性能计数器就会从 1 MB 以下跃升至大约 90 MB。我还知道,在负载相对较重的系统下,通常在运行一段时间(几天)后,这个序列化过程发生了几次,当调用这个序列化方法时,应用程序会抛出内存异常,即使有似乎是足够的内存。我猜碎片化是问题所在(虽然我不能说我 100% 确定,但我非常接近)

我能想到的最简单的短期修复(我想我正在寻找短期和长期答案)是在我完成序列化过程后立即调用 GC.Collect。在我看来,这将从 LOH 中垃圾收集对象,并且可能会在其他对象添加到它之前这样做。这将允许其他对象紧密贴合堆中的剩余对象,而不会造成太多碎片。

除了这个荒谬的 90MB 分配之外,我认为我没有其他任何使用 LOH 丢失的东西。这种 90 MB 的分配也比较少见(大约每 4 小时一次)。当然,我们仍然会有 1.5 MB 的数组,可能还有一些其他更小的序列化对象。

有什么想法吗?

由于良好的反应而更新

这是我完成工作的代码。实际上,我已经尝试将其更改为压缩 WHILE 序列化,以便序列化同时序列化为流,但我没有得到更好的结果。我还尝试将内存流预分配到 100 MB,并尝试连续两次使用相同的流,无论如何 LOH 都会上升到 180 MB。我正在使用 Process Explorer 来监控它。这太疯狂了。我想接下来我会尝试 UnmanagedMemoryStream 的想法。

如果您不愿意,我会鼓励你们尝试一下。它不必是这个确切的代码。只需序列化一个大数据集,你就会得到令人惊讶的结果(我的有很多表,大约 15 个,还有很多字符串和列)

        byte[] bytes;
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
        new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();            
        System.IO.MemoryStream memStream = new System.IO.MemoryStream();
        serializer.Serialize(memStream, obj);
        bytes = CompressionHelper.CompressBytes(memStream.ToArray());
        memStream.Dispose();
        return bytes;

尝试使用 UnmanagedMemoryStream 进行二进制序列化后更新

即使我序列化为 UnmanagedMemoryStream,LOH 也会跳到相同的大小。看来无论我做什么,调用 BinaryFormatter 来序列化这个大对象都会使用 LOH。至于预分配,它似乎没有多大帮助。说我预分配说我预分配 100MB,然后我序列化,它将使用 170MB。这是代码。比上面的代码还要简单

BinaryFormatter serializer  = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(1024*1024*100);
GC.Collect();
serializer.Serialize(memoryStream, assetDS);

中间的 GC.Collect() 只是用来更新 LOH 性能计数器。您将看到它将分配正确的 100 MB。但是当您调用 serialize 时,您会注意到它似乎将它添加到您已经分配的 100 个之上。

【问题讨论】:

  • 有什么办法可以直接序列化到Stream,而不是使用 LOH 作为缓冲区?

标签: c# memory-management out-of-memory fragmentation large-object-heap


【解决方案1】:

90MB 的 RAM 并不多。

除非遇到问题,否则请避免调用 GC.Collect。如果您遇到问题并且没有更好的解决方法,请尝试调用 GC.Collect 并查看您的问题是否已解决。

【讨论】:

    【解决方案2】:

    注意集合类和流(如 MemoryStream)在 .NET 中的工作方式。它们有一个底层缓冲区,一个简单的数组。每当集合或流缓冲区增长到超出数组的分配大小时,数组就会重新分配,现在是之前大小的两倍。

    这可能会导致 LOH 中的数组存在多个副本。您的 14MB 数据集将开始使用 128KB 的 LOH,然后再使用 256KB,然后再使用 512KB,依此类推。最后一个,即实际使用的那个,大约为 16MB。 LOH 包含这些的总和,大约 30MB,其中只有一个在实际使用中。

    在没有 gen2 集合的情况下执行此操作 3 次,您的 LOH 已增长到 90MB。

    通过将缓冲区预分配到预期大小来避免这种情况。 MemoryStream 有一个构造函数,它采用初始容量。所有的集合类也是如此。在清空所有引用后调用 GC.Collect() 有助于疏通 LOH 并清除那些中间缓冲区,但代价是过早阻塞 gen1 和 gen2 堆。

    【讨论】:

      【解决方案3】:

      如果您确实需要将 LOH 用于服务之类的事情或需要长时间运行的事情,您需要使用永远不会释放的缓冲池,并且您可以在启动时进行理想分配。当然,这意味着您必须自己进行“内存管理”。

      根据您使用此内存所做的操作,您可能还必须 p/Invoke 到选定部分的本机代码,以避免不得不调用一些 .NET API 来强制您将数据放在新分配的空间中LOH。

      这是一篇关于这些问题的很好的起点文章:https://devblogs.microsoft.com/dotnet/using-gc-efficiently-part-3/

      如果你的 GC 技巧能奏效,我会认为你非常幸运,而且它真的只有在系统中同时发生的事情不多的情况下才会奏效。如果您有工作并行进行,这只会稍微延迟不可避免的情况。

      还阅读了有关 GC.Collect.IIRC 的文档,GC.Collect(n) 只说它收集的时间不超过第 n 代——并不是说它实际上曾经到达第 n 代。

      【讨论】:

        【解决方案4】:

        不用担心 LOH 大小会增加。担心分配/取消分配 LOH。 .Net 对 LOH 非常愚蠢——它不是在远离常规堆的地方分配 LOH 对象,而是在下一个可用的 VM 页面上分配。我有一个 3D 应用程序,它对 LOH 和常规对象进行大量分配/解除分配——结果(如 DebugDiag 转储报告中所见)是小堆和大堆的页面最终在整个 RAM 中交替,直到没有大块剩余 2 GB VM 空间。可能的解决方案是分配一次你需要的东西,然后不要释放它——下次再使用它。

        使用 DebugDiag 分析您的流程。了解 VM 地址如何逐渐向 2 GB 地址标记攀升。然后做出改变以防止这种情况发生。

        【讨论】:

          【解决方案5】:

          我同意这里的其他一些海报,您可能希望尝试使用技巧来使用 .NET Framework,而不是试图通过 GC.Collect 强制它与您一起使用。

          您可能会发现此Channel 9 video 很有帮助,其中讨论了减轻垃圾收集器压力的方法。

          【讨论】:

            【解决方案6】:

            不幸的是,我可以解决此问题的唯一方法是将数据分成块,以免在 LOH 上分配大块。这里提出的所有答案都很好,并且预计会起作用,但事实并非如此。似乎 .NET 中的二进制序列化(使用 .NET 2.0 SP2)在后台发挥了自己的小魔力,阻止了用户控制内存分配。

            那么对这个问题的回答是“这不太可能奏效”。在使用 .NET 序列化时,最好的办法是将大对象序列化为较小的块。对于所有其他情况,上面提到的答案都很棒。

            【讨论】:

              猜你喜欢
              • 2010-10-15
              • 1970-01-01
              • 2011-12-22
              • 2011-11-14
              • 1970-01-01
              • 2012-08-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-12
              相关资源
              最近更新 更多