C# VS2015升级后的垃圾收集和Parallel.ForEach问题

C# VS2015升级后的垃圾收集和Parallel.ForEach问题,c#,garbage-collection,visual-studio-2015,parallel.foreach,.net-4.6,C#,Garbage Collection,Visual Studio 2015,Parallel.foreach,.net 4.6,我有一些代码,可以在我自己的类似R的C#DataFrame类中处理数百万个数据行。有许多Parallel.ForEach调用用于并行迭代数据行。这段代码已经使用VS2013和.NET 4.5运行了一年多,没有任何问题 我有两台开发机器(A和B),最近将机器A升级到VS2015。大约有一半的时间,我开始注意到我的代码中有一个奇怪的间歇性冻结。让它运行很长一段时间,结果证明代码最终完成了。它只需要15-120分钟,而不是1-2分钟 由于某些原因,尝试使用VS2015调试器中断所有调试都会失败。所以我

我有一些代码,可以在我自己的类似R的C#DataFrame类中处理数百万个数据行。有许多Parallel.ForEach调用用于并行迭代数据行。这段代码已经使用VS2013和.NET 4.5运行了一年多,没有任何问题

我有两台开发机器(A和B),最近将机器A升级到VS2015。大约有一半的时间,我开始注意到我的代码中有一个奇怪的间歇性冻结。让它运行很长一段时间,结果证明代码最终完成了。它只需要15-120分钟,而不是1-2分钟

由于某些原因,尝试使用VS2015调试器中断所有调试都会失败。所以我插入了一堆日志语句。事实证明,在Parallel.ForEach循环期间存在Gen2集合时(比较每个Parallel.ForEach循环前后的集合计数),会发生此冻结。所有额外的13-118分钟都花在任何并行中。ForEach循环调用恰好与Gen2集合(如果有)重叠。如果在任何Parallel.ForEach循环过程中都没有Gen2集合(我运行它时大约有50%的时间),那么一切都会在1-2分钟内完成

当我在机器A上运行VS2013中的相同代码时,我得到了相同的冻结。当我在机器B(从未升级)上运行VS2013中的代码时,它工作得非常好。它在一夜之间运行了几十次,没有结冰

我注意到/尝试过的一些事情:

  • 冻结是在机器A上附加或不附加调试器的情况下发生的(起初我认为这是VS2015调试器的问题)
  • 无论我是在调试模式还是发布模式下构建,冻结都会发生
  • 如果我以.NET4.5或.NET4.6为目标,就会发生冻结
  • 我试着禁用龙井,但这并没有影响冻结
我根本没有更改默认的GC设置。根据GCSettings,所有运行都是以LatencyMode Interactive和IsServerGC为false进行的

我可以在每次调用Parallel.ForEach之前切换到LowLatency,但我更愿意了解发生了什么

在VS2015升级后,有没有其他人见过Parallel.ForEach中出现奇怪的冻结?有什么好的下一步的想法吗

更新1:在上面模糊的解释中添加一些示例代码…

下面是一些示例代码,我希望能够演示这个问题。此代码在B机器上持续运行10-12秒。它遇到了许多Gen2集合,但它们几乎不花时间。如果我取消注释这两个GC设置行,我可以强制它没有Gen2集合。它比30-50秒慢一些

现在在我的A机器上,代码需要随机的时间。似乎在5到30分钟之间。而且它遇到的Gen2系列越多,情况似乎就越糟。如果我取消注释两个GC设置行,在机器A上也需要30-50秒(与机器B相同)

这可能需要在行数和数组大小方面进行一些调整,才能在另一台机器上显示

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Runtime;    

public class MyDataRow
{
    public int Id { get; set; }
    public double Value { get; set; }
    public double DerivedValuesSum { get; set; }
    public double[] DerivedValues { get; set; }
}

class Program
{
    static void Example()
    {
        const int numRows = 2000000;
        const int tempArraySize = 250;

        var r = new Random();
        var dataFrame = new List<MyDataRow>(numRows);

        for (int i = 0; i < numRows; i++) dataFrame.Add(new MyDataRow { Id = i, Value = r.NextDouble() });

        Stopwatch stw = Stopwatch.StartNew();

        int gcs0Initial = GC.CollectionCount(0);
        int gcs1Initial = GC.CollectionCount(1);
        int gcs2Initial = GC.CollectionCount(2);

        //GCSettings.LatencyMode = GCLatencyMode.LowLatency;

        Parallel.ForEach(dataFrame, dr =>
        {
            double[] tempArray = new double[tempArraySize];
            for (int j = 0; j < tempArraySize; j++) tempArray[j] = Math.Pow(dr.Value, j);
            dr.DerivedValuesSum = tempArray.Sum();
            dr.DerivedValues = tempArray.ToArray();
        });

        int gcs0Final = GC.CollectionCount(0);
        int gcs1Final = GC.CollectionCount(1);
        int gcs2Final = GC.CollectionCount(2);

        stw.Stop();

        //GCSettings.LatencyMode = GCLatencyMode.Interactive;

        Console.Out.WriteLine("ElapsedTime = {0} Seconds ({1} Minutes)", stw.Elapsed.TotalSeconds, stw.Elapsed.TotalMinutes);

        Console.Out.WriteLine("Gcs0 = {0} = {1} - {2}", gcs0Final - gcs0Initial, gcs0Final, gcs0Initial);
        Console.Out.WriteLine("Gcs1 = {0} = {1} - {2}", gcs1Final - gcs1Initial, gcs1Final, gcs1Initial);
        Console.Out.WriteLine("Gcs2 = {0} = {1} - {2}", gcs2Final - gcs2Initial, gcs2Final, gcs2Initial);

        Console.Out.WriteLine("Press Any Key To Exit...");
        Console.In.ReadLine();
    }

    static void Main(string[] args)
    {
        Example();
    }
}
使用系统;
使用系统集合;
使用System.Collections.Generic;
使用System.IO;
使用系统诊断;
使用系统线程;
使用System.Threading.Tasks;
使用System.Linq;
使用系统运行时;
公共类MyDataRow
{
公共int Id{get;set;}
公共双值{get;set;}
公共双派生值sum{get;set;}
public double[]DerivedValues{get;set;}
}
班级计划
{
静态void示例()
{
常数int numRows=2000000;
常数int tempArraySize=250;
var r=新的随机变量();
var dataFrame=新列表(numRows);
对于(inti=0;i
{
double[]tempArray=新的double[tempArraySize];
对于(int j=0;j
更新2:只是为了将评论中的内容转移给未来的读者…

这个修补程序:完全解决了这个问题。在申请之后,我没有发现任何缓慢的问题


事实证明,它与Parallel.ForEach没有任何关系,我相信这是基于这样的原因:尽管修补程序确实出于某种原因提到了Parallel.ForEach。

这确实执行得非常糟糕,但后台GC在这里并没有给您带来好处。我注意到的第一件事是Parallel.ForEach()使用了太多的任务。线程池管理器将线程行为误解为“被I/O困住”,并启动额外的线程。这使问题变得更糟。这方面的解决办法是:

var options = new ParallelOptions();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;

Parallel.ForEach(dataFrame, options, dr => {
    // etc..
}
这使我们能够更好地了解什么是疾病
    for (int i = 0; i < numRows; i++) dataFrame.Add(new MyDataRow { 
        Id = i, Value = r.NextDouble(), 
        DerivedValues = new double[tempArraySize] });

    ...
    Parallel.ForEach(dataFrame, options, dr => {
        var array = dr.DerivedValues;
        for (int j = 0; j < array.Length; j++) array[j] = Math.Pow(dr.Value, j);
        dr.DerivedValuesSum = array.Sum();
    });
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
    </startup>
<runtime>
    <gcConcurrent enabled="false" />
</runtime>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
    </startup>
<runtime>
    <gcServer enabled="true" />
</runtime>
</configuration>