Parallel.ForEach vs Task.Factory.StartNew - PullRequest
255 голосов
/ 15 февраля 2011

В чем разница между приведенными ниже фрагментами кода?Разве оба не будут использовать потоки пула потоков?

Например, если я хочу вызвать функцию для каждого элемента в коллекции,

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}

Ответы [ 4 ]

287 голосов
/ 15 февраля 2011

Первый вариант гораздо лучше.

Parallel.ForEach внутренне использует Partitioner<T> для распределения вашей коллекции по рабочим элементам.Он не будет выполнять одну задачу для каждого элемента, а вместо этого будет выполнять пакетную обработку, чтобы снизить накладные расходы.

Второй вариант запланирует один Task для элемента в вашей коллекции.Хотя результаты будут (почти) одинаковыми, это приведет к гораздо большему количеству служебных данных, чем это необходимо, особенно для больших коллекций, и приведет к тому, что общее время выполнения будет медленнее.соответствующий перегружает Parallel.ForEach , если это необходимо.Подробнее см. Пользовательские разделы в MSDN.

Основное отличие во время выполнения - второй будет работать асинхронно.Это можно продублировать с помощью Parallel.ForEach, выполнив:

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));

Делая это, вы по-прежнему пользуетесь разделителями, но не блокируете до завершения операции.

81 голосов
/ 18 июня 2013

Я провел небольшой эксперимент по запуску метода «1000000000» раз с «Parallel.For» и один с объектами «Задача».

Я измерил время процессора и нашел Parallel более эффективным.Parallel.For разделяет вашу задачу на небольшие рабочие элементы и выполняет их параллельно на всех ядрах оптимальным образом.При создании большого количества объектов задач (FYI TPL будет использовать внутренний пул потоков) будет переносить каждое выполнение каждой задачи, создавая больше напряжения в окне, что видно из эксперимента ниже.

Я также создал небольшое видео, в котором объясняется базовый TPL, а также демонстрируется, как Parallel.For использует ваше ядро ​​более эффективно http://www.youtube.com/watch?v=No7QqSc5cl8 по сравнению с обычными задачами и потоками.

Эксперимент1

Parallel.For(0, 1000000000, x => Method1());

Эксперимент 2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}

Processor time comparison

17 голосов
/ 15 февраля 2011

Parallel.ForEach оптимизирует (может даже не запускать новые потоки) и блокирует, пока цикл не завершится, и Task.Factory явно создаст новый экземпляр задачи для каждого элемента и возвратит его до завершения (асинхронные задачи).Parallel.Foreach гораздо эффективнее.

8 голосов
/ 28 ноября 2017

На мой взгляд, наиболее реалистичный сценарий - это когда задачи требуют тяжелой операции. Подход Shivprasad фокусируется больше на создании объектов / распределении памяти, чем на самих вычислениях. Я провел исследование, назвав следующий метод:

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}

Выполнение этого метода занимает около 0,5 с.

Я назвал это 200 раз, используя Parallel:

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});

Тогда я назвал это 200 раз, используя старомодный способ:

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 

Первый случай завершен за 26656мс, второй за 24478мс. Я повторил это много раз. Каждый раз второй подход незначительно быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...