Параллельный .ForEach против Task.Factory.StartNew
В чем разница между приведенными ниже фрагментами кода? Не будут ли использоваться потоки threadpool?
Например, если я хочу вызвать функцию для каждого элемента в коллекции,
Parallel.ForEach<Item>(items, item => DoSomething(item));
vs
foreach(var item in items)
{
Task.Factory.StartNew(() => DoSomething(item));
}
Ответы
Ответ 1
Первый вариант гораздо лучше.
Parallel.ForEach, внутри, использует Partitioner<T>
, чтобы распространять вашу коллекцию в рабочие элементы. Он не будет выполнять одну задачу для каждого элемента, а скорее, чтобы уменьшить задействованные служебные данные.
Второй вариант запланирует один Task
для каждого элемента в вашей коллекции. Хотя результаты будут (почти) одинаковыми, это приведет к гораздо более накладным расходам, чем необходимо, особенно для больших коллекций, и приведет к замедлению общего времени выполнения.
FYI. Используемый Partitioner можно контролировать, используя соответствующие перегрузки в Parallel.ForEach, если это необходимо. Подробнее см. Пользовательские разделители в MSDN.
Основное различие, во время выполнения, второе будет действовать асинхронно. Это можно продублировать с помощью Parallel.ForEach, выполнив следующие действия:
Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
Выполняя это, вы по-прежнему пользуетесь разделителями, но не блокируете до завершения операции.
Ответ 2
Я сделал небольшой эксперимент по запуску метода "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]()
Ответ 3
Parallel.ForEach будет оптимизировать (возможно, даже не запускать новые потоки) и блокировать до тех пор, пока цикл не будет завершен, а Task.Factory явно создаст новый экземпляр задачи для каждого элемента и вернется до их завершения (асинхронные задачи).
Parallel.Foreach намного эффективнее.
Ответ 4
На мой взгляд, наиболее реалистичным сценарием является то, что задачи выполняют тяжелую операцию. Подход 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мс. Я повторил это много раз. Каждый раз, когда второй подход маргиналист быстрее.