Параметры по умолчанию для Parallel.ForEach
хорошо работают только тогда, когда задача связана с ЦП и линейно масштабируется . Когда задача связана с процессором, все работает отлично. Если у вас четырехъядерный процессор и другие процессы не запущены, то Parallel.ForEach
использует все четыре процессора. Если у вас есть четырехъядерный процессор, и какой-то другой процесс на вашем компьютере использует один полный процессор, то Parallel.ForEach
использует примерно три процессора.
Но если задача не связана с процессором, то Parallel.ForEach
продолжает запускать задачи, изо всех сил стараясь сохранить занятость всех процессоров. Тем не менее, независимо от того, сколько задач выполняется параллельно, всегда остается больше неиспользуемой мощности ЦП, и поэтому он продолжает создавать задачи.
Как вы можете определить, связана ли ваша задача с ЦП? Надеюсь, просто проверив это. Если вы учитываете простые числа, это очевидно. Но другие случаи не так очевидны. Эмпирическим способом определить, связана ли ваша задача с ЦП, является ограничение максимальной степени параллелизма с помощью ParallelOptions.MaximumDegreeOfParallelism
и наблюдение за поведением вашей программы. Если ваша задача связана с центральным процессором, вы должны увидеть такой шаблон в четырехъядерной системе:
ParallelOptions.MaximumDegreeOfParallelism = 1
: использовать один полный ЦП или 25% загрузки ЦП
ParallelOptions.MaximumDegreeOfParallelism = 2
: использовать два ЦП или 50% загрузки ЦП
ParallelOptions.MaximumDegreeOfParallelism = 4
: использовать все процессоры или использовать процессор на 100%
Если он ведет себя так, вы можете использовать параметры по умолчанию Parallel.ForEach
и получить хорошие результаты. Линейное использование ЦП означает хорошее планирование задач.
Но если я запускаю ваше приложение на моем Intel i7, я получаю около 20% загрузки ЦП независимо от того, какую максимальную степень параллелизма я установил. Почему это? Так много памяти выделяется, что сборщик мусора блокирует потоки. Приложение связано с ресурсами, а ресурс является памятью.
Аналогично, задача, связанная с вводом-выводом, которая выполняет длительные запросы к серверу базы данных, также никогда не сможет эффективно использовать все ресурсы ЦП, доступные на локальном компьютере. И в подобных случаях планировщик задач не может «знать, когда остановить» запуск новых задач.
Если ваша задача не связана с ЦП или загрузка ЦП не масштабируется линейно с максимальной степенью параллелизма, то вам следует советовать Parallel.ForEach
не запускать слишком много задач одновременно. Самый простой способ - указать число, которое допускает некоторый параллелизм для перекрывающихся задач, связанных с вводом / выводом, но не настолько, чтобы вы подавляли потребность локального компьютера в ресурсах или перегружали любые удаленные серверы. Для получения наилучших результатов используется метод проб и ошибок:
static void Main(string[] args)
{
Parallel.ForEach(CreateData(),
new ParallelOptions { MaxDegreeOfParallelism = 4 },
(data) =>
{
data[0] = 1;
});
}