Использование PLINQ вызывает исключение OutofMemory, но не с LINQ?


Я наткнулся на сценарий, где использование LINQ работает нормально, но PLINQ вызывает "OutOfMemoryException". Ниже приведен пример кода

   static void Main(string[] args)
    {
        Stopwatch timer = new Stopwatch();

        var guidList = new List<Guid>();
        for (int i = 0; i < 10000000; i++)
        {
            guidList.Add(Guid.NewGuid());
        }
       timer.Start();

        // var groupedList = guidList.GroupBy(f => f).Where(g => g.Count() > 1);
        var groupedList = guidList.AsParallel().GroupBy(f => f).Where(g => g.Count() > 1);
        timer.Stop();

        Console.WriteLine(string.Format("Took {0} ms time with result: {1} duplications", timer.ElapsedMilliseconds, groupedList.Count()));
        Console.ReadKey();
    } 

Выбрасывание внутреннего исключения " исключение типа 'System.OutOfMemoryException "был выброшен"..в чем может быть проблема? Каковы рекомендации по использованию PLINQ для этого типа сценария, заранее спасибо.

2 2

2 ответа:

Вы, вероятно, близки к исчерпанию памяти в обычной версии Linq, но использование AsParallel() добавит дополнительные накладные расходы на секционирование, чтобы работать параллельно, и из-за этого вы превысите лимит.

Когда я попробовал ваш образец, у меня сначала были те же результаты, непараллельная версия заканчивалась, но версия PLinq заканчивалась памятью-удвоение размера списка Guid затем приводило к тому, что обе версии заканчивались памятью. Также обратите внимание, что 10 миллионов GUID занимают около 152 МБ пространства в память

Также обратите внимание, что ваши текущие запросы plinq и linq выполняются только в вашем Console.WriteLine() - поскольку Linq ленив, вы должны принудительно выполнить оценку, т. е. используя ToList() (или в вашем случае Count())

Один из способов, по крайней мере, ослабить проблему, состоит в том, чтобы не помещать все GUID в список, а использовать перечисляемый.

public IEnumerable<Guid> getGuids(int number)
{
    for (int i = 0; i < number; i++)
    {
        yield return Guid.NewGuid();
    }
}

Это имеет несколько преимуществ. Во-первых, он лениво загружен, поэтому вы потерпите неудачу в середине обработки, а не в объявлении GUID. Во-вторых, вы не удерживаете все GUID, которые не соответствуют предложению where; они могут быть удалены из памяти. Это очень много значит. Вам нужно будет иметь только одну копию каждого guid в памяти, а не две, когда вы нажмете предложение WHERE.