У меня есть код для обработки нескольких миллионов строк данных в моем собственном R-подобном классе С# DataFrame. Там несколько Parallel.ForEach вызывают параллельную итерацию строк данных. Этот код работает более года, используя VS2013 и .NET 4.5 без проблем.
У меня есть две dev-машины (A и B) и недавно обновленная машина A до VS2015. Я начал замечать странное прерывистое замораживание в моем коде примерно в половине случаев. Позволяя ему работать в течение длительного времени, оказывается, что код в конечном итоге заканчивается. Это займет всего 15-120 минут вместо 1-2 минут.
Попытки разбить все, используя отладчик VS2015, по какой-то причине не работают. Поэтому я вставил кучу операторов журнала. Оказывается, это замораживание происходит, когда есть коллекция Gen2 во время цикла Parallel.ForEach(сравнивая счетчик коллекции до и после каждого цикла Parallel.ForEach). Все дополнительные 13-118 минут тратятся внутри того, что Parallel.ForEach цикл звонит, случается, перекрываться с коллекцией Gen2 (если есть). Если нет коллекций Gen2 во время любых циклов Parallel.ForEach(около 50% времени, когда я запускаю его), все заканчивается отлично через 1-2 минуты.
Когда я запускаю тот же код в VS2013 на машине A, я получаю такие же зависания. Когда я запускаю код в VS2013 на машине B (который никогда не обновлялся), он работает отлично. Он провел десятки раз в ночное время без замерзания.
Некоторые вещи, которые я заметил/попробовал:
- Замораживание происходит с или без отладчика, подключенного к машине A (я понял, что это было что-то с отладчиком VS2015)
- Замораживание происходит, если я создаю режим Debug или Release
- Замедление происходит, если я нацелен на .NET 4.5 или .NET 4.6
- Я попытался отключить RyuJIT, но это не повлияло на зависание
Я вообще не изменяю настройки GC по умолчанию. Согласно GCSettings, все прогоны происходят с LatencyMode Interactive и IsServerGC как false.
Я могу просто переключиться на LowLatency перед каждым вызовом Parallel.ForEach, но я бы предпочел понять, что происходит.
Кто-нибудь еще видел странные зависания в Parallel.ForEach после обновления VS2015? Любые идеи о том, какой был бы следующий следующий шаг?
ОБНОВЛЕНИЕ 1: добавление некоторого примера кода в туманное объяснение выше...
Вот пример кода, который, я надеюсь, продемонстрирует эту проблему. Этот код работает через 10-12 секунд на машине B, последовательно. Он сталкивается с рядом коллекций Gen2, но у них почти нет времени. Если я раскомментирую две строки настроек GC, я могу заставить ее не иметь коллекций Gen2. Он несколько медленнее, чем через 30-50 секунд.
Теперь на моей машине A код занимает произвольное количество времени. Кажется, от 5 до 30 минут. И, похоже, все хуже, чем больше коллекций Gen2, с которыми он сталкивается. Если я раскомментирую две линии настройки GC, она занимает 30-50 секунд и на машине A (так же, как на машине 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();
}
}
ОБНОВЛЕНИЕ 2: просто чтобы переместить вещи из комментариев для будущих читателей...
Это исправление: https://support.microsoft.com/en-us/kb/3088957 полностью устраняет проблему. Я не вижу никаких проблем с медлительностью после применения.
Оказалось, что не имеет ничего общего с Parallel.ForEach, на мой взгляд, на основе этого: http://blogs.msdn.com/b/maoni/archive/2015/08/12/gen2-free-list-changes-in-clr-4-6-gc.aspx, хотя в исправлении упоминается Parallel.ForEach по какой-либо причине.