适用于收集和压缩其他流程的情况

时间:2018-05-18 11:47:09

标签: c# memory-management garbage-collection

很抱歉进入"为什么要收集?"再次。在C#进程中,通常不鼓励手动收集内存以释放RAM。我总体上同意。

要做到这一点,你需要:

为什么这是无用的(并且可能是反效率的)被调用了几个原因。我总体上同意,但我不知道这是否有危险。

如果我们不这样做,它会发生(至少对于服务器GC和.NET 4.5),系统可能会为您的进程专用几GB物理内存,而这些物理内存并非真正被活动对象使用。特别是LOH可能永远不会被压缩并且使用大量实际上没有C#内存管理器观点的RAM。我每天都会遇到这样的问题:系统专用的物理内存为20 GB,在几小时前的RAM密集型计算高峰期使用,但只有一小部分仍在使用中。

据我所知,机器上仍然没有明显的性能问题。通常认为这绝不是一个问题,我(几乎)同意它。论证的主要观点是" free"进程无法访问LOH中的RAM,因此系统(Windows)至少在RAM不足时将其移至磁盘。这个答案解释得非常好:When is memory, allocated by .NET process, released back to Windows

从其他流程的角度来看......仍有一些"担心"。

我看到的第一点是,如果另一个进程对RAM有紧急需求,系统需要时间将未使用的内存传输到磁盘。预防性压缩可能会阻止它。

但另一点更令人担忧。系统真的可以将未使用的RAM移动到磁盘吗?如果LOH碎片化,99%的可用空间与1%的已用空间交错,该怎么办?我猜这个系统适用于大段,因此几乎没有任何段实际上是" 0%使用"。也许它更复杂或者可能是错误的。你怎么知道的?

我对那些对此有一定经验的人感兴趣。您是否观察过理论"您不需要收集"出了问题,出于某种原因它已经被证明是健康的吗? (你知道为什么)

2 个答案:

答案 0 :(得分:2)

在现代操作系统中,分配RAM 1 。您(您的进程)在自己的地址空间内分配。操作系统如何使用物理RAM备份(必要时),这在很大程度上是透明的。

您在 地址空间中所做的事情与其他流程完全无关。

另请参阅:Raymond Chen的挑衅性标题为It's the address space stupid 2

  

系统真的可以将未使用的RAM移动到磁盘吗?

这不是“已使用”或“未使用”RAM的问题。当操作系统 3 在物理页面上运行不足时,它会使用一些合适的策略识别要驱逐的页面(例如,Least Recently Used是一个简单的解决方案)。然后它必须决定如何处理页面。

如果幸运的话,它挑选的页面要么是文件映像(例如可执行文件的一部分)支持,要么是因为它最后写入磁盘而未被更改。如果是这种情况,它可以在不执行任何额外I / O的情况下逐出页面。如果它运气不好,它会安排I / O,然后继续寻找免费的页面。

它一直在 4 执行此操作 4 。而且它还在做其他事情,例如使用其他未使用的页面作为文件系统缓存。

您遇到真正问题的唯一时间是主动工作的所有页面的总和(每个流程的工作集合的总和)大于RAM页面,通常可用于进程。此时,由于系统不断将页面写入磁盘并检索它们,因此您将发生颠簸。但是在这一点上,GC几乎肯定无济于事,因为我们知道所有这些页面都在积极使用 - 它们都包含可到达的对象。

1 有一些方法可以要求地址空间始终由真正的RAM支持并且永远不会交换,但这不是通常所做的事情,你必须故意不做事来实现这一点

2 我知道这是对政治报价的重复,但这并不意味着这里的所有读者都必须认识到这一点,过去有些人认为我推荐这篇文章是真的我字面上称他们为愚蠢。

3 我试图在这里成为一般人,但我认识到我可能会偏离“任何”操作系统将在Windows中专门做什么。

4 PDC10: Mysteries of Windows Memory Management Revealed: Part Two

答案 1 :(得分:1)

我已经在这个问题上工作了一段时间。结论是:

  • 就RAM消耗而言,LOH碎片仅影响很小
  • 这“一点”在某些情况下很重要

本文说压缩大对象堆Large Object Heap Compaction: Should You Use it?可能很有用。它说了很多真实的东西,实际上很有用,但是有点肤浅。压缩LOH的有用性必须与系统对虚拟内存的分页一起研究。

总结文章:

默认情况下,大对象堆从不压缩。压缩LOH可以提高您自己的过程的性能,从而更快地分配大型对象。最重要的是,它允许其他进程使用更多的RAM。 但这是有代价的:压缩LOH不可忽略。这就是为什么默认行为是不压缩它的原因。您必须权衡利弊,并找到适当的策略来触发压缩:就像每隔XX分钟一样。

现在,我将解释为什么这是几乎错误的。您需要阅读这篇文章,解释如何管理虚拟内存:Physical and Virtual Memory in Windows 10

如果LOH内部的某些块是空闲的,则将永远无法访问它们。因此,系统能够将这些块移出RAM。真实对象使用的LOH中的每个活动(已使用)块的大小均大于85k。页面大小为4k(在32位和64位Windows中均是):这些页面中的每一个都可以移出RAM。一个简单的推理可以告诉您,无法从RAM中移出的LOH中的可用空间的比例始终很小。要么您有较大的可用块,然后它们的大多数页面都可以移出RAM,要么您有较小的可用块,然后它们的大小比已用的块小。在这两种情况下,LOH中都没有太多不能移出RAM的可用空间。

我测试了它。我编写了一个创建大小为10 GB的分段LOH的过程,其中只有1%处于活动状态(100 MB),将1 mb的活动块与100 mb的空闲块交织在一起。只有1%的内存被积极地用于计算。在我的16 GB笔记本电脑上,我可以顺利运行其中的三个过程。他们并不慢。当第一个启动时,系统为它提供了10 GB的工作集。当第二个过程开始时,该工作集减少了,而对两个过程的速度没有很大的影响。同样的第三。几分钟后,每个进程的工作集达到了预期的100 MB。他们的虚拟内存只有10 GB。

每个过程的巡航速度都非常理想。但是系统需要将这些GB写入磁盘。这确实减慢了启动过程的速度。这花费了时间和资源。页面文件增长。压缩每个过程的LOH本来是更好的策略。因此,是的,压缩LOH更好,但是系统的默认策略相当好。可能还有一些更细微的问题:当您重新使用已移动到磁盘的空闲块时,究竟会发生什么:系统是否需要从文件中读取它?

还有与性能无关的实际问题。当您在一家在数百台计算机上运行数百个进程的公司中工作时,您想要监视和检测潜在的RAM溢出。对于零散的堆,测量工作集的大小会产生误导。 .NET不能轻松访问“可达对象的总大小”。因此,当您看到一个“使用” 10 GB(工作集大小)的进程时,您不知道这10 GB是否确实需要。压缩LOH有助于使指标“工作集大小”切实可用。

注意:这是用于我的实验的代码:

public static class ExampleFragmentation
{
    public static void Main()
    {
        var mega = 1024 * 1024;
        var smallArrays = new int[100][];
        var largeArrays = new int[100][];
        for (int i = 0; i < 100; i++)
        {
            smallArrays[i] = CreateArray(mega);
            largeArrays[i] = CreateArray(100 * mega);
        }
        largeArrays = null;

        while (true)
        {
            DateTime start = DateTime.Now;
            long sum = 0;
            for (int i = 0; i < 100; i++)
                foreach (var array in smallArrays)
                    foreach (var element in array)
                        sum += element;
            double time = (DateTime.Now - start).TotalSeconds;
            Console.WriteLine("sum=" + sum + "  " + time + " s");
        }
    }

    private static int[] CreateArray(int bytes)
    {
        var array = new int[bytes / sizeof(int)];
        for (int i = 0; i < array.Length; i++)
            array[i] = 1;
        return array;
    }
}