Производительность запуска Parallel.Foreach на нескольких потоках - PullRequest
5 голосов
/ 23 марта 2011

У меня есть 3 основных потока обработки, каждый из которых выполняет операции со значениями ConcurrentDictionaries с помощью Parallel.Foreach.Размер словарей варьируется от 1000 до 250 000 элементов

TaskFactory factory = new TaskFactory();
Task t1 = factory.StartNew(() =>
{
        Parallel.ForEach(dict1.Values, item => ProcessItem(item));
});

Task t2 = factory.StartNew(() =>
{                                
        Parallel.ForEach(dict2.Values, item => ProcessItem(item));
});

Task t3 = factory.StartNew(() =>
{                           
        Parallel.ForEach(dict3.Values, item => ProcessItem(item));
});
t1.Wait();
t2.Wait();
t3.Wait();

Я сравнил производительность (общее время выполнения) этой конструкции с простым запуском Parallel.Foreach в главном потоке, и производительность значительно улучшилась (время выполнения было сокращено примерно в 5 раз)

Мои вопросы:

  1. Что-то не так с подходом выше?Если да, что и как можно улучшить?
  2. В чем причина разного времени выполнения?
  3. Что является хорошим способом для отладки / анализа такой ситуации?

РЕДАКТИРОВАТЬ : Чтобы еще больше прояснить ситуацию: я издеваюсь над клиентскими вызовами службы WCF, каждый из которых приходит в отдельный поток (причина для Задач).Я также попытался использовать ThreadPool.QueueUserWorkItem вместо Task, без повышения производительности.Объекты в словаре имеют от 20 до 200 свойств (только десятичные дроби и строки), и в них нет операций ввода-вывода

Я решил проблему, поставив в очередь запросы на обработку в BlockingCollection и обрабатывая их по одному за раз

Ответы [ 3 ]

7 голосов
/ 23 марта 2011

Вы, вероятно, чрезмерно распараллеливаете.

Вам не нужно создавать 3 задачи, если вы уже используете хорошее (и сбалансированное) распараллеливание внутри каждой из них.

Parallel.Foreach уже пытаются использовать правильное количество потоков, чтобы использовать весь потенциал ЦП без насыщения. И, создавая другие задачи, имеющие Parallel.Foreach, вы, вероятно, насыщаете его.
(РЕДАКТИРОВАТЬ: как сказал Хенк , у них, вероятно, есть некоторые проблемы с согласованием количества потоков, порождаемых при параллельной работе, и, по крайней мере, это приводит к большим издержкам).

Посмотрите здесь для некоторых подсказок.

3 голосов
/ 23 марта 2011

Прежде всего, Задача не является Потоком.

Ваши Parallel.ForEach() вызовы выполняются планировщиком, который использует ThreadPool и должен попытаться оптимизировать использование потока. ForEach применяет Разделителя. Когда вы запускаете их параллельно, они не могут хорошо координироваться.

Только в случае проблем с производительностью рассмотрите возможность помощи с дополнительными задачами или директивами DegreeOfParallelism. А затем всегда сначала анализируйте и анализируйте.

Объяснить ваши результаты сложно, это может быть вызвано многими факторами (например, вводом / выводом), но преимущество «единственной главной задачи» состоит в том, что планировщик имеет больше контроля, а процессор и кэш используются лучше. (местонахождение).

2 голосов
/ 23 марта 2011

Словари сильно различаются по размеру, и по внешнему виду (учитывая, что все заканчивается через <5 с), объем обрабатывающей работы невелик. Не зная больше, трудно сказать, что на самом деле происходит. Насколько велики ваши элементы словаря? Сценарий основного потока, с которым вы сравниваете это, выглядит так? </p>

Parallel.ForEach(dict1.Values, item => ProcessItem(item)); 
Parallel.ForEach(dict2.Values, item => ProcessItem(item)); 
Parallel.ForEach(dict3.Values, item => ProcessItem(item)); 

Добавляя Задачи вокруг каждого ForEach, вы добавляете дополнительные накладные расходы для управления задачами и, вероятно, вызываете конфликт памяти, поскольку dict1, dict2 и dict3 все пытаются одновременно находиться в памяти и в горячем кеше. Помните, что циклы процессора дешевы, а ошибки кеша - нет.

...