为什么通过GCLatencyMode.LowLatency最小化垃圾收集会对执行时间产生负面影响?

时间:2018-02-06 09:26:47

标签: c# multithreading memory-management garbage-collection heap

我很困惑为什么将GCSettings.LatencyMode设置为GCLatencyMode.LowLatency会对执行时间产生负面影响?

请考虑以下代码。请注意,我在线程池中有足够的线程,所以我确保这里没有引入延迟。此外,我在这台机器上有足够的内存。 InteractiveLowLatency之间的差异导致LowLatency的执行时间增加了3倍。

class Program
{
    static void Main(string[] args)
    {
        //capture current latency mode
        var currentLatencyMode = GCSettings.LatencyMode;

        //set low latency mode to minimize garbage collection
        GCSettings.LatencyMode = GCLatencyMode.LowLatency;

        var watch = new Stopwatch();
        var numberTasksToSpinOff = 4;
        var numberItems = 20000;
        var random = new Random((int)DateTime.Now.Ticks);
        var dataPoints = Enumerable.Range(1, numberItems).Select(x => random.NextDouble()).ToList();
        var workers = new List<Worker>();

        //structure workers
        for (int i = 1; i <= numberTasksToSpinOff; i++)
        {
            workers.Add(new Worker(i, dataPoints));
        }

        //start timer
        watch.Restart();

        //parallel work
        if (workers.Any())
        {
            var processorCount = Environment.ProcessorCount;
            var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = processorCount };
            Parallel.ForEach(workers, parallelOptions, DoSomeWork);
        }

        //stop timer
        watch.Stop();

        //reset latency mode
        GCSettings.LatencyMode = currentLatencyMode;

        Console.WriteLine($"Time it took to complete in Milliseconds: {watch.ElapsedMilliseconds}");
        Console.WriteLine("Press key to quit");
        Console.ReadLine();
    }

    private static void DoSomeWork(Worker worker)
    {
        Console.WriteLine($"WorkerId: {worker.WorkerId} -> New Tasks spun off with in Thread Id: {Thread.CurrentThread.ManagedThreadId}");

        var indexPos = 0;
        foreach (var dp in worker.DataPoints)
        {
            var subset = worker.DataPoints.Skip(indexPos).Take(worker.DataPoints.Count - indexPos).ToList();
            indexPos++;
        }
    }
}

public class Worker
{
    public int WorkerId { get; set; }
    public List<double> DataPoints { get; set; }

    public Worker(int workerId, List<double> dataPoints)
    {
        WorkerId = workerId;
        DataPoints = dataPoints;
    }
}

1 个答案:

答案 0 :(得分:3)

这里没有免费的午餐,垃圾收集者必须做一份工作,并试图考虑你的顾虑。然而,没有一种尺寸适合所有(特别是当试图突破其极限时)。

Latency Modes

  

要回收对象,垃圾收集器必须停止所有执行   应用程序中的线程。在某些情况下,例如当   应用程序检索数据或显示内容,一个完整的垃圾   收集可以在关键时刻发生并阻碍性能。您   可以通过设置来调整垃圾收集器的侵入性   GCSettings.LatencyMode属性之一   System.Runtime.GCLatencyMode

更多

  

LowLatency 会抑制第2代集合并仅执行   第0代和第1代集合。它只能用于短时间   时间在较长时期内,如果系统处于内存压力之下,   垃圾收集器将触发一个集合,可以简单地   暂停应用程序并中断对时间要求严格的操作。这个   设置仅适用于工作站垃圾回收。

在低延迟期间,除非发生以下情况,否则将抑制第2代集合:

  • 系统从操作系统收到内存不足通知。
  • 您的应用程序代码通过调用GC.Collect方法并为生成参数指定2来诱导集合。

使用低延迟的指南

  

使用LowLatency模式时,请考虑以下准则:

     
      
  1. 尽可能缩短低延迟的时间段。

  2.   
  3. 避免在低延迟期间分配大量内存。由于垃圾回收,可能会发生内存不足通知   回收更少的物品。

  4.   
  5. 在低延迟模式下,最小化您所分配的数量,特别是在大对象堆上的分配和   固定物品。

  6.   
  7. 注意可能分配的线程。因为LatencyMode属性设置是进程范围的,所以您可以生成一个   任何可能分配的线程的OutOfMemoryException。

  8.   
  9. ...

  10.   

根据指南(考虑到您之前的问题,How to properly parallelize worker tasks?),您显然是在尝试将其用于预期的理想操作条件。

我认为最重要的一点是1和3,显然垃圾收集器要么被gc.collect命令强制清理,要么觉得它需要清理你正在使用的大量内存分配,即11演出。

这里的关键是,不知道垃圾收集器的确切内部和工作情况,并且确切地知道你在做什么以及为什么,你的问题可能没有一个理想的答案来说除了“在你的你的情况会影响执行时间“