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.
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? –
si1 y si2 están uno al lado del otro. ¿Compartir falsamente? –
@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