Mi escenario de aplicación es el siguiente: quiero evaluar la ganancia de rendimiento que se puede lograr en una máquina de cuatro núcleos para procesar la misma cantidad de datos. Tengo dos configuraciones siguientes:Necesito ideas sobre el perfil de multi-threading en C en Linux
i) 1-Process: programa sin ningún tipo de subprocesamiento y procesa datos de 1M .. 1G, mientras que se suponía que el sistema ejecutaba solo un núcleo de sus 4 núcleos.
ii) 4-hilos-Proceso: Un programa con 4 hilos (todos los hilos que realizan la misma operación) pero que procesa el 25% de los datos de entrada.
En mi programa para crear 4 hilos, utilicé las opciones predeterminadas de pthread (es decir, sin ningún pthread_attr_t específico). Creo que la ganancia de rendimiento de la configuración de 4 hilos en comparación con la configuración de 1 proceso debería ser más cercana al 400% (o en algún lugar entre 350% y 400%).
I perfilado el tiempo de creación de roscas igual que esta a continuación:
timer_start(&threadCreationTimer);
pthread_create(&thread0, NULL, fun0, NULL);
pthread_create(&thread1, NULL, fun1, NULL);
pthread_create(&thread2, NULL, fun2, NULL);
pthread_create(&thread3, NULL, fun3, NULL);
threadCreationTime = timer_stop(&threadCreationTimer);
pthread_join(&thread0, NULL);
pthread_join(&thread1, NULL);
pthread_join(&thread2, NULL);
pthread_join(&thread3, NULL);
Desde aumento en el tamaño de los datos de entrada también puede aumentar en el requisito de memoria de cada hilo, que así la carga de todos los datos de antemano definitivamente no es una opción viable. Por lo tanto, para asegurarnos de no aumentar los requisitos de memoria de cada hilo, cada hilo lee los datos en trozos pequeños, los procesa y lee el siguiente fragmento, los procesa, etc. Por lo tanto, la estructura del código de mis funciones a cargo de las discusiones es la siguiente:
timer_start(&threadTimer[i]);
while(!dataFinished[i])
{
threadTime[i] += timer_stop(&threadTimer[i]);
data_source();
timer_start(&threadTimer[i]);
process();
}
threadTime[i] += timer_stop(&threadTimer[i]);
variable dataFinished[i]
está marcado por true
proceso cuando la recibió y procesar todos los datos necesarios. Process()
sabe cuándo hacer eso :-)
En la función principal, estoy calculando el tiempo que tarda configuración de 4-roscado de la siguiente manera:
execTime4Thread = max(threadTime[0], threadTime[1], threadTime[2], threadTime[3]) + threadCreationTime
.
Y ganancia de rendimiento se calcula simplemente
gain = execTime1process/execTime4Thread * 100
Problema: En pequeño tamaño de los datos alrededor de 1 M a 4 M, la ganancia de rendimiento es generalmente buena (entre 350% y 400%). Sin embargo, la tendencia de ganancia de rendimiento disminuye exponencialmente con el aumento en el tamaño de entrada. Sigue disminuyendo hasta un tamaño de datos de hasta 50M o más, y luego se estabiliza en torno al 200%. Una vez que llegó a ese punto, permanece casi estable incluso para 1 GB de datos.
Mi pregunta es: ¿alguien puede sugerir el razonamiento principal de este comportamiento (es decir, la disminución del rendimiento al principio y permanecer estable más tarde)?
Y sugerencias sobre cómo solucionarlo?
Para su información, también investigué el comportamiento de threadCreationTime
y threadTime
para cada subproceso para ver qué está sucediendo. Para 1M de datos, los valores de estas variables son pequeños pero con el aumento en el tamaño de datos estas dos variables aumentan exponencialmente (pero threadCreationTime
debe permanecer casi igual independientemente del tamaño de datos y threadTime
debe aumentar a una velocidad correspondiente a los datos procesados).Después de seguir aumentando hasta 50M o menos threadCreationTime
se vuelve estable y threadTime
(al igual que la caída del rendimiento se vuelve estable) y threadCreationTime
seguir aumentando a una velocidad constante correspondiente al aumento de los datos a procesar (que se considera comprensible).
¿Crees que aumentar el tamaño de la pila de cada hilo, procesar elementos de prioridad o valores personalizados de otros parámetros tipo de planificador (utilizando pthread_attr_init
) puede ayudar?
PD: Los resultados se obtienen al ejecutar los programas en modo a prueba de fallos de Linux con raíz (es decir, el sistema operativo mínimo se ejecuta sin interfaz gráfica de usuario y cosas de red).
¿Cuál es el modelo de su CPU? – Tudor
Contaminación cruzada de caché entre hilos. ¿Has intentado variar el tamaño de los fragmentos de datos? También debe incluir la carga de datos en sus mediciones, ya que puede ser un cuello de botella, es decir, 2 núcleos pueden saturar su bus de memoria. (Además, si todavía no lo hace, debe poner sus temporizadores en diferentes líneas de caché.) – Mats
@Mats: El procesador es Intel (R) Core (TM) 2 CPU cuádruple Q9950 @ 2.83GHz. No, no he verificado el tamaño del fragmento de datos. OK, intentaré cambiar el tamaño del fragmento de datos. Sin embargo, no entendí a qué se refería con cache-lines. ¿Cómo poner temporizadores en la memoria caché? – Junaid