2011-12-08 7 views
7

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).

+0

¿Cuál es el modelo de su CPU? – Tudor

+4

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

+0

@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

Respuesta

2

Desde incremento en el tamaño de los datos de entrada también puede aumentar en el requisito de memoria de cada hilo, entonces, por lo tanto, cargar todos los datos por adelantado definitivamente no es una opción viable. Por lo tanto, con el fin de garantizar no para aumentar el requisito de memoria de cada hilo, cada hilo lee datos en trozos pequeños, procesarlo y leer el siguiente fragmento procesarlo y y así sucesivamente.

Sólo esto, solo, puede causar una drástica disminución de velocidad .

Si hay suficiente memoria, leer una gran cantidad de datos de entrada siempre será más rápido que leer datos en trozos pequeños, especialmente de cada hilo. Cualquier beneficio de E/S de fragmentación (efectos de almacenamiento en caché) desaparece cuando lo descompone en pedazos. Incluso la asignación de un gran trozo de memoria es mucho más barata que la asignación de trozos pequeños muchas, muchas veces.

Como control de cordura, puede ejecutar htop para asegurarse de que al menos todos sus núcleos se completen durante la ejecución. De lo contrario, su cuello de botella podría estar fuera de su código multihilo.

Dentro de la rosca,

  • contexto enhebrar cambia debido a los muchos hilos pueden causar sub-óptima aceleración
  • como han mencionado otros, un caché fría debido a la no lectura de memoria contigua pueden causar retrasos

Pero volviendo a leer su OP, sospecho que la ralentización tiene algo que ver con su asignación de entrada/memoria de datos. ¿De dónde exactamente estás leyendo tus datos? ¿Algún tipo de zócalo? ¿Estás seguro de que necesitas asignar memoria más de una vez en tu hilo?

Algún algoritmo en sus subprocesos de trabajo es probable que sea subóptimo/costoso.

0

¿Está su hilo comenzando en la creación? Si es el caso, ocurrirá lo siguiente:

mientras su hilo padre está creando un hilo, el hilo ya creado comenzará a ejecutarse. Cuando toca timerStop (ThreadCreation timer), los cuatro ya han ejecutado durante un tiempo determinado. Entonces threadCreationTime se superpone a threadTime[i]

Como está ahora, no sabes lo que estás midiendo. Esto no resolverá su problema, porque obviamente tiene un problema ya que threadTime no aumenta linealmente, pero al menos no agregará tiempos superpuestos.

Para obtener más información, puede usar perf tool si está disponible en su distribución. por ejemplo:

perf stat -e cache-misses <your_prog> 

y ver lo que ocurre con una versión de dos hilos, una versión de tres hilo etc ...

+0

Este problema persiste incluso si no considero 'threadCreationTime' y solo considero' threadTime [i] '(que ahora se ha dividido en variables separadas siguiendo las sugerencias anteriores sobre cache-lines). Después de seguir esa sugerencia, los resultados mejoraron pero ahora se ha cambiado el cuello de botella. Es decir, en datos de 1M la ganancia de rendimiento es buena. Pero en 2M se cae y luego permanece igual incluso para 1G. También intentaré su sugerencia para ver cache-misses. ¿Crees que Valgrind puede ayudar? También estoy pensando en probar Intel vTune profiler. – Junaid

+0

@Junaid: cache-misses es solo un ejemplo, hay mucho contador para ver. – shodanex