c# - programming - Parallel.ForEach vs Task.Factory.StartNew
tpl c# (4)
En mi opinión, el escenario más realista es cuando las tareas tienen una operación pesada que completar. El enfoque de Shivprasad se centra más en la creación de objetos / asignación de memoria que en la informática en sí. Hice una investigación llamando al siguiente método:
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;
}
La ejecución de este método toma alrededor de 0.5 seg.
Lo llamé 200 veces usando paralelo:
Parallel.For(0, 200, (int i) =>
{
SumRootN(10);
});
Luego lo llamé 200 veces usando la manera antigua:
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());
Primer caso completado en 26656ms, el segundo en 24478ms. Lo repetí muchas veces. Cada vez que el segundo enfoque es marginalmente más rápido.
¿Cuál es la diferencia entre los siguientes fragmentos de código? ¿No estarán ambos usando hilos de hilos?
Por ejemplo, si quiero llamar a una función para cada elemento de una colección,
Parallel.ForEach<Item>(items, item => DoSomething(item));
vs
foreach(var item in items)
{
Task.Factory.StartNew(() => DoSomething(item));
}
Hice un pequeño experimento de ejecutar un método "1000000000" veces con "Parallel.For" y uno con objetos "Task".
Medí el tiempo del procesador y encontré el paralelo más eficiente. Parallel.For divide su tarea en pequeños elementos de trabajo y los ejecuta en todos los núcleos de forma paralela de una manera óptima. Mientras se crean muchos objetos de tareas (FYI TPL utilizará la agrupación de subprocesos internamente) moverá cada ejecución en cada tarea, lo que creará más tensión en el cuadro, lo que se evidencia en el experimento a continuación.
También he creado un pequeño video que explica el TPL básico y también demostró cómo Parallel.For utiliza su núcleo de manera más eficiente http://www.youtube.com/watch?v=No7QqSc5cl8 en comparación con las tareas y subprocesos normales.
Experimento 1
Parallel.For(0, 1000000000, x => Method1());
Experimento 2
for (int i = 0; i < 1000000000; i++)
{
Task o = new Task(Method1);
o.Start();
}
La primera es una opción mucho mejor.
Parallel.ForEach, internamente, usa un Partitioner<T>
para distribuir su colección en elementos de trabajo. No realizará una tarea por elemento, sino que se encargará de esto para reducir los gastos generales involucrados.
La segunda opción programará una sola Task
por elemento en su colección. Si bien los resultados serán (casi) los mismos, esto supondrá una sobrecarga mucho mayor de la necesaria, especialmente para grandes colecciones, y hará que los tiempos de ejecución generales sean más lentos.
Para su información: el particionador utilizado se puede controlar mediante el uso de las sobrecargas apropiadas para Parallel.ForEach , si así lo desea. Para obtener más información, consulte Particiones personalizadas en MSDN.
La principal diferencia, en tiempo de ejecución, es que la segunda actuará de forma asíncrona. Esto se puede duplicar usando Parallel.ForEach haciendo:
Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
Al hacer esto, aún se aprovecha de los particionadores, pero no bloquea hasta que se complete la operación.
Parallel.ForEach optimizará (puede que ni siquiera inicie nuevos subprocesos) y se bloqueará hasta que finalice el ciclo, y Task.Factory creará explícitamente una nueva instancia de tarea para cada elemento y regresará antes de que finalice (tareas asíncronas). Parallel.Foreach es mucho más eficiente.