2010-08-26 14 views
10

He estado probando el rendimiento de System.Threading.Parallel frente a Threading y me sorprende ver que Parallel tarda más tiempo en terminar las tareas que en el enhebrado. Estoy seguro de que se debe a mi conocimiento limitado de Parallel, que acabo de comenzar a leer.C# Paralelo vs. Ejecución del código con subprocesos

Pensé que voy a compartir algunos fragmentos y si alguien puede señalarme, el código paralelo se ejecuta más lento que el código enhebrado. También intenté ejecutar la misma comparación para encontrar números primos y encontré el código paralelo que termina mucho más tarde que el código enhebrado.

public class ThreadFactory 
{ 
    int workersCount; 
    private List<Thread> threads = new List<Thread>(); 

    public ThreadFactory(int threadCount, int workCount, Action<int, int, string> action) 
    { 
     workersCount = threadCount; 

     int totalWorkLoad = workCount; 
     int workLoad = totalWorkLoad/workersCount; 
     int extraLoad = totalWorkLoad % workersCount; 

     for (int i = 0; i < workersCount; i++) 
     { 
      int min, max; 
      if (i < (workersCount - 1)) 
      { 
       min = (i * workLoad); 
       max = ((i * workLoad) + workLoad - 1); 
      } 
      else 
      { 
       min = (i * workLoad); 
       max = (i * workLoad) + (workLoad - 1 + extraLoad); 
      } 
      string name = "Working Thread#" + i; 

      Thread worker = new Thread(() => { action(min, max, name); }); 
      worker.Name = name; 
      threads.Add(worker); 
     } 
    } 

    public void StartWorking() 
    { 
     foreach (Thread thread in threads) 
     { 
      thread.Start(); 
     } 

     foreach (Thread thread in threads) 
     { 
      thread.Join(); 
     } 
    } 
} 

Este es el programa:

Stopwatch watch = new Stopwatch(); 
watch.Start(); 
int path = 1; 

List<int> numbers = new List<int>(Enumerable.Range(0, 10000)); 

if (path == 1) 
{ 
    Parallel.ForEach(numbers, x => 
    { 
     Console.WriteLine(x); 
     Thread.Sleep(1); 

    }); 
} 
else 
{ 
    ThreadFactory workers = new ThreadFactory(10, numbers.Count, (min, max, text) => { 

     for (int i = min; i <= max; i++) 
     { 
      Console.WriteLine(numbers[i]); 
      Thread.Sleep(1); 
     } 
    }); 

    workers.StartWorking(); 
} 

watch.Stop(); 
Console.WriteLine(watch.Elapsed.TotalSeconds.ToString()); 

Console.ReadLine(); 

Actualización:

Tomando en consideración Bloqueo: He probado el siguiente fragmento. De nuevo los mismos resultados, Parallel parece terminar mucho más lento.

ruta = 1; cieling = 10000000;

List<int> numbers = new List<int>(); 

    if (path == 1) 
    { 
     Parallel.For(0, cieling, x => 
     { 
      lock (numbers) 
      { 
       numbers.Add(x);  
      } 

     }); 
    } 

    else 
    { 
     ThreadFactory workers = new ThreadFactory(10, cieling, (min, max, text) => 
     { 

      for (int i = min; i <= max; i++) 
      { 
       lock (numbers) 
       { 
        numbers.Add(i);  
       }      

      } 
     }); 

     workers.StartWorking(); 
    } 

Actualización 2: Sólo una rápida actualización que mi máquina tiene procesador de cuatro núcleos. Entonces Parallel tiene 4 núcleos disponibles.

+1

No debe bloquear el ForEach, lo hace internamente. Pero el uso de un ReaderWriterLockSlim lo volverá a hacer rápidamente;) –

+0

Establezca ThreadFactory en 2 hilos y configure la concurrencia máxima en el Paralelo.Para 2, elimine Console.WriteLine y haga algo más apropiado. Ahora, ¿cómo se comparan? Prueba 3 y 3; 4 y 4; ... En algún momento, Parallel.ForEach decidirá que tiene asignados suficientes hilos y asignará menos del máximo que usted le indique, pero al menos hasta ese punto estará comparando los tiempos usando el * mismo * número de hilos. –

+0

@Hightechrider: Bueno, en términos de arrojar carga de trabajo real, como mencioné en mi pregunta, probé esto contra encontrar números primos también, que bastante procesador intensivo, muestra el 100% de actividad en todo momento, y descubrí que ThreadFactory funciona más rápido. Pruébelo y vea ... Incluso traté de configurar el conteo de hilos a 2,3, etc. Los mismos resultados. – ace

Respuesta

3

refiriéndose a un blog post por Reed Copsey Jr:

Parallel.ForEach es un poco más complicado, sin embargo. Cuando se trabaja con un IEnumerable genérico, la cantidad de elementos necesarios para el procesamiento no se conoce de antemano y debe descubrirse en tiempo de ejecución. Además, dado que no tenemos acceso directo a cada elemento, el planificador debe enumerar la colección para procesarlo. Dado que IEnumerable no es seguro para subprocesos, debe bloquear los elementos mientras enumera, crear colecciones temporales para cada fragmento para procesar y programarlo en.

El bloqueo y la copia pueden hacer que Parallel.ForEach tarde más tiempo. También el particionamiento y el programador de ForEach podrían impactar y generar gastos generales. Probé tu código y aumenté la duración de cada tarea, y luego los resultados están más cerca, pero aún así ForEach es más lento.

[Editar - más investigación]

que añade lo siguiente a los bucles de ejecución:

if (Thread.CurrentThread.ManagedThreadId > maxThreadId) 
    maxThreadId = Thread.CurrentThread.ManagedThreadId; 

Lo que esto demuestra en mi máquina es que utiliza 10 hilos menos con ParaCada, en comparación con el otro con la configuración actual. Si desea obtener más hilos de ForEach, tendrá que jugar con ParallelOptions y el Programador.

Ver Does Parallel.ForEach limits the number of active threads?

+0

Intresting ... déjame probar la comparación con la inserción en una lista con bloqueo habilitado. Thnx. – ace

+0

Vi su actualización ... y modifiqué mi respuesta un poco. –

+0

Editado una vez más :) Se reduce al número de subprocesos que se utilizan. –

3

Creo que puedo responder a su pregunta. En primer lugar, no escribió cuántos núcleos tiene su sistema. si está ejecutando un doble núcleo, solo 4 subprocesos funcionarán usando el Paralelo. Mientras está trabajando con 10 subprocesos en su ejemplo de Subproceso.Más hilos funcionarán mejor ya que la tarea que está ejecutando (Impresión + Dormir brevemente) es una tarea muy corta para enhebrar y la carga del hilo es muy grande en comparación con la tarea, estoy casi seguro de que si escribe el mismo código sin hilos funcionará más rápido.

Ambos métodos funcionan más o menos de la misma manera, pero si crea todos los subprocesos de antemano, guardará los asignados como el Paralelo.Para usar el grupo de Tareas que agrega un poco de sobrecarga de movimiento.

+0

+1: la pregunta es una comparación total de manzanas vs. naranjas porque usa una cantidad diferente de hilos. Console.WriteLine también es una mala elección para un caso de prueba. –

+0

Tengo un quad core. @Hightechrider Incluso lo probé para encontrar números primos. El mismo resultado. Pruebe el ejemplo de mi código y vea los resultados. – ace

+0

Recordatorio de nuevo: la única promesa de Parallel-s es hacer la programación "mejor que a mano". Sin eso, no es de mucha utilidad, excepto como una posible sintaxis de conveniencia. – ZXX

0

La comparación no es muy justa con respecto a Threading.Parallel. Le dice a su grupo de hilos personalizado que necesitará 10 hilos. Threading.Parallel no sabe cuántos hilos necesitará, por lo que intenta adaptarse en el tiempo de ejecución teniendo en cuenta cosas como la carga actual de la CPU y otras cosas. Dado que el número de iteraciones en la prueba es lo suficientemente pequeño, puede aplicar este número de penalización de adaptación de hilos. Proporcionando la misma pista para Threading.Parallel será hacer que se ejecute mucho más rápido:

 

int workerThreads; 
int completionPortThreads; 
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads); 
ThreadPool.SetMinThreads(10, completionPortThreads); 
 
+0

Thnx ... intentaré eso y veré si hace la diferencia. – ace

+0

No olvide que la única promesa de Parallel-s es hacer la programación "mejor que a mano". Sin embargo, gran cosa para el caso nulo: una cuchara más para alimentar :-) – ZXX

0

Es lógico :-)

que sería la primera vez en la historia que la adición de uno (o dos) capas de código desempeño mejorado. Cuando utiliza bibliotecas de conveniencia, debe esperar pagar el precio. Por cierto, no has publicado los números. Tengo que publicar los resultados :-)

Para hacer que las cosas sean un poco más fallidas (o sesgadas :-) para los Paralelos, convierta la lista en una matriz.

Luego, para hacerlos totalmente injustos, divida el trabajo por su cuenta, haga una selección de solo 10 elementos y participe totalmente de acciones de alimentación en Paralelo. Por supuesto, estás haciendo el trabajo que Parallel-s prometió hacer por ti en este momento, pero seguramente será un número interesante :-)

BTW Acabo de leer el blog de Reed. La partición utilizada en esta pregunta es lo que él llama la partición más simple e ingenua. Lo que lo convierte en una muy buena prueba de eliminación. Aún necesita verificar el caso de trabajo cero solo para saber si está totalmente regado.

+0

jaja ... bueno, si estoy haciendo todo el trabajo, ¿eso derrota el rito de puntos? – ace

+0

Te da la medida para decirte si Parallel-s puede al menos ser más rápido cuando no están haciendo ningún trabajo. Piensa en ello como una pregunta de eliminación :-) Al menos esa parte funciona bien, entonces podrían ser viable como una característica de conveniencia. Si no, descubriste temprano qué dll evitar. – ZXX

+0

no estoy seguro si estoy listo para darme por vencido todavía, debería haber una mejor optimización, ajustes que me faltan al hacer mis llamadas paralelas. – ace

Cuestiones relacionadas