2012-04-11 17 views
6

Soy nuevo en multiprocesamiento e intento aprenderlo a través de un programa simple, que agrega 1 a ny devuelve la suma. En el caso secuencial, el main llama a la función sumFrom1 dos veces para n = 1e5 y 2e5; en los casos multiproceso, se crean dos subprocesos usando pthread_create y dos sumas se calculan en subprocesos separados. La versión de multiprocesamiento es mucho más lenta que la versión secuencial (ver resultados a continuación). Ejecuto esto en una plataforma de 12 CPU y no hay comunicación entre hilos.¿Por qué el multithreading es más lento que la programación secuencial en mi caso?

multiproceso:

Thread 1 returns: 0 
Thread 2 returns: 0 
sum of 1..10000: 50005000 
sum of 1..20000: 200010000 
time: 156 seconds 

secuencial:

sum of 1..10000: 50005000 
sum of 1..20000: 200010000 
time: 56 seconds 

Cuando agrego -O2 en la compilación, el momento de la versión multiproceso (9s) es menor que la de la versión secuencial (11s) , pero no mucho como espero. Siempre puedo tener el indicador -O2 activado, pero tengo curiosidad sobre la baja velocidad del multihilo en el caso no optimizado. ¿Debería ser más lento que la versión secuencial? Si no, ¿qué puedo hacer para que sea más rápido?

El código:

#include <stdio.h> 
#include <pthread.h> 
#include <time.h> 

typedef struct my_struct 
{ 
    int n;                                        
    int sum;                                        
}my_struct_t;                                       

void *sumFrom1(void* sit)                                    
{                                          
    my_struct_t* local_sit = (my_struct_t*) sit;                               
    int i;                                        
    int nsim = 500000; // Loops for consuming time                                     
    int j;                                        

    for(j = 0; j < nsim; j++)                                   
    {                                         
    local_sit->sum = 0;                                     
    for(i = 0; i <= local_sit->n; i++)                                 
     local_sit->sum += i;                                    
    }  
} 

int main(int argc, char *argv[])                                  
{                                          
    pthread_t thread1;                                    
    pthread_t thread2;                                    
    my_struct_t si1;                                     
    my_struct_t si2;                                     
    int   iret1;                                     
    int   iret2;                                     
    time_t  t1;                                      
    time_t  t2;                                      


    si1.n = 10000;                                      
    si2.n = 20000;                                      

    if(argc == 2 && atoi(argv[1]) == 1) // Use "./prog 1" to test the time of multithreaded version                                 
    {                                         
    t1 = time(0);                                      
    iret1 = pthread_create(&thread1, NULL, sumFrom1, (void*)&si1);  
    iret2 = pthread_create(&thread2, NULL, sumFrom1, (void*)&si2);                          
    pthread_join(thread1, NULL);                                  
    pthread_join(thread2, NULL);                                  
    t2 = time(0);                                      

    printf("Thread 1 returns: %d\n",iret1);                               
    printf("Thread 2 returns: %d\n",iret2);                               
    printf("sum of 1..%d: %d\n", si1.n, si1.sum);                              
    printf("sum of 1..%d: %d\n", si2.n, si2.sum);                              
    printf("time: %d seconds", t2 - t1);                                

    }                                         
    else  // Use "./prog" to test the time of sequential version                                       
    {                                         
    t1 = time(0);                                      
    sumFrom1((void*)&si1);                                    
    sumFrom1((void*)&si2);                                    
    t2 = time(0);                                      

    printf("sum of 1..%d: %d\n", si1.n, si1.sum);                              
    printf("sum of 1..%d: %d\n", si2.n, si2.sum);                              
    printf("time: %d seconds", t2 - t1); 
    }                        
    return 0;                       
} 

Update1:

Después de googlear un poco de "falsa compartir" (Gracias, James @ Martin!), Creo que es la causa principal. Hay (al menos) dos maneras de solucionarlo:

La primera forma es la inserción de una zona de separación entre las dos estructuras (Gracias, @dasblinkenlight):

my_struct_t si1; 
char   memHolder[4096]; 
my_struct_t si2; 

Sin -O2, el tiempo de el consumo disminuye de ~ 156 s a ~ 38 s.

La segunda manera es evitar con frecuencia actualizar sit->sum, que se puede realizar utilizando una variable TEMP en sumFrom1 (como respondió @Jens Gustedt):

for(int sum = 0, j = 0; j < nsim; j++)    
{ 
    sum = 0; 
    for(i = 0; i <= local_sit->n; i++) 
    sum += i; 
} 
local_sit->sum = sum; 

Sin -O2, el consumo de tiempo disminuye desde ~ 156s a ~ 35s o ~ 109s (¡Tiene dos picos! No sé por qué). Con -O2, el tiempo consume ~ 8s.

+0

En tales pruebas, tenemos que promediar los resultados. ¿Cuántas veces ejecutó las pruebas con la optimización de -O2? Y si has corrido varias veces, ¿cuál es el promedio de veces? –

+2

si1 y si2 están uno al lado del otro. ¿Compartir falsamente? –

+0

@PavanManjunath Gracias por el consejo. Corrí 10 veces con -O2. los tiempos medios son 7.9s para la versión multiproceso y 11.7s para la secuencia. La fluctación es pequeña. – cogitovita

Respuesta

3

Al modificar su código para

typedef struct my_struct 
{ 
    size_t n; 
    size_t sum; 
}my_struct_t; 

void *sumFrom1(void* sit) 
{ 
    my_struct_t* local_sit = sit; 
    size_t nsim = 500000; // Loops for consuming time 
    size_t n = local_sit->n; 
    size_t sum = 0; 
    for(size_t j = 0; j < nsim; j++) 
    { 
    for(size_t i = 0; i <= n; i++) 
     sum += i; 
    } 
    local_sit->sum = sum; 
    return 0; 
} 

el fenómeno desaparece. Los problemas que tuvo:

  • utilizando int como un tipo de datos es completamente incorrecto para una prueba de este tipo. Sus figuras fueron tales que la suma se desbordó. El desbordamiento de tipos firmados es un comportamiento indefinido. Tienes suerte de que no comió tu almuerzo.
  • tienen límites y las variables de suma con direccionamiento indirecto que compra cargas y almacenamientos adicionales, que en caso de -O0 se hace realmente como tales , con todas las implicaciones de falsa compartir y cosas por el estilo.

Su código también observaron otros errores:

  • una falta incluir, por atoi
  • fundido superflouous hacia y desde void*
  • la impresión de time_t como int

favor compilar su código con -Wall antes pos ting.

+0

) El uso de 'size_t sum = 0;' da como resultado un refuerzo significativo de preformance. Luego agregar esto 'size_t n = local_sit-> n;' reduce la velocidad de nuevo. ¿Alguna idea de por qué? (Todo compilado con -O0) – alk

+1

No, en realidad no. No creo que tenga sentido hablar sobre el código no optimizado con ese detalle. Si quieres saber qué está pasando realmente, un primer paso sería mira en el ensamblador que se genera con '-S'. –

Cuestiones relacionadas