Paralelo.ForEach vs Task.Fábrica.StartNew


¿Cuál es la diferencia entre los siguientes fragmentos de código? ¿No usarán ambos hilos threadpool?

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));
}
Author: stackoverflowuser, 2011-02-15

4 answers

La primera es una opción mucho mejor.

Paralelo.ForEach, internamente, utiliza un Partitioner<T> para distribuir su colección en elementos de trabajo. No hará una tarea por elemento, sino que lo hará por lotes para reducir la sobrecarga involucrada.

La segunda opción programará un único Task por artículo de tu colección. Si bien los resultados serán (casi) los mismos, esto introducirá una sobrecarga mucho mayor de la necesaria, especialmente para colecciones grandes, y hará que los tiempos de ejecución generales sé más lento.

FYI - El Particionador utilizado puede ser controlado usando las sobrecargas apropiadas en Paralelo.ForEach , si así lo desea. Para obtener más información, consulte Particionadores personalizados en MSDN.

La principal diferencia, en tiempo de ejecución, es que el segundo actuará asíncrono. Esto se puede duplicar usando Paralelo.ForEach haciendo:

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

Al hacer esto, todavía se aprovecha de los particionadores, pero no se bloquea hasta que se complete la operación.

 270
Author: Reed Copsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-15 20:37:25

Hice un pequeño experimento de ejecutar un método "1000000000" veces con "Paralelo.Para "y uno con objetos" Task".

Medí el tiempo del procesador y encontré que Parallel era más eficiente. Paralelo.Para 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 que la creación de muchos objetos de tarea (FYI TPL utilizará thread pooling internamente) moverá cada ejecución en cada tarea creando más estrés en el cuadro que es evidente de la experimento a continuación.

También he creado un pequeño video que explica TPL básico y también demostró cómo Paralelo.Para utiliza su núcleo de manera más eficiente http://www.youtube.com/watch?v=No7QqSc5cl8 en comparación con las tareas e hilos 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();
}

Comparación del tiempo del procesador

 73
Author: Shivprasad Koirala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-29 03:45:47

Paralelo.ForEach optimizará (puede que ni siquiera inicie nuevos hilos) y bloqueará hasta que el bucle haya terminado, y la tarea.Factory creará explícitamente una nueva instancia de tarea para cada elemento y la devolverá antes de que terminen (tareas asíncronas). Paralelo.Foreach es mucho más eficiente.

 16
Author: Sogger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-15 20:39:19

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 computación misma. 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 aproximadamente 0.5 seg.

Lo llamé 200 veces usando Paralelo:

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

Entonces 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 el segundo enfoque es marginalmente más rápido.

 7
Author: user1089583,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-28 13:23:40