2012-01-17 12 views
20

Tengo un vector<int> con 10,000,000 (10 millones) de elementos, y que mi estación de trabajo tiene cuatro núcleos. Hay una función, llamada ThrFunc, que opera en un número entero. Supongamos que el tiempo de ejecución para ThrFunc para cada número entero en el vector<int> es aproximadamente el mismo.¿Cuál es la mejor manera de determinar el número de hilos para disparar en una máquina con n núcleos? (C++)

¿Cómo debo determinar la cantidad óptima de hilos para disparar? ¿La respuesta es tan simple como la cantidad de elementos dividida por el número de núcleos? ¿O hay un cálculo más sutil?

Edición para proporcionar información adicional

  • No hay necesidad de bloqueo; Cada función de las necesidades de invocación de sólo lectura solamente acceso
+2

¡Eso sería un montón de hilos! Creo que querías decir la cantidad de núcleos, ¿verdad? – dasblinkenlight

+0

Suponiendo que todas las operaciones en los enteros pueden suceder completamente al mismo tiempo, simplemente se divide por el número de núcleos. Es mucho más difícil estimar cuándo el trabajo no se puede hacer al mismo tiempo. –

+1

¿Están estos subprocesos realizando alguna E/S (bloqueo) o cualquier operación de bloqueo, como las comunicaciones de red o la base de datos? Si no, entonces es probable que el número óptimo de núcleos sea N. En su caso, 4. De lo contrario, vale la pena experimentar con 2N o 3N, mientras que un hilo está haciendo E/S, otro hilo puede funcionar. – selbie

Respuesta

23

El número óptimo de hilos es probable que sea o bien el número de núcleos en la máquina o el número de núcleos veces dos.

En términos más abstractos, desea obtener el mayor rendimiento posible. Obtener el rendimiento más alto requiere la menor cantidad de puntos de contención entre los hilos (dado que el problema original es trivialmente paralelizable). Es probable que el número de puntos de contención sea el número de subprocesos que comparten un núcleo o el doble, ya que un núcleo puede ejecutar uno o dos subprocesos lógicos (dos con hyperthreading).

Si su carga de trabajo hace uso de un recurso del que tiene menos de cuatro disponibles (ALU en el acceso al disco duro Bulldozer?), Entonces el número de subprocesos que debe crear estará limitado por eso.

La mejor manera de encontrar la respuesta correcta es, con todas las preguntas sobre el hardware, para probar y descubrir.

+0

Gracias por la respuesta. Aceptado. – Shredderroy

+0

Si sus cálculos utilizarán los mismos datos en cada hilo, probablemente sea mejor ignorar el hyperthreading o incluso deshabilitarlo por completo. Es probable que los datos de ambos hilos se almacenen en caché con bastante rapidez, por lo que ninguno se parará, por lo que HT nunca tendrá tiempo para hacer nada. –

+0

+1 Un gran consejo. – Tudor

4

Suponiendo que ThrFunc está vinculado a la CPU, entonces probablemente desee un hilo por núcleo y divida los elementos entre ellos.

Si la función tiene un elemento de E/S, la respuesta es más complicada, ya que puede tener uno o más hilos por núcleo esperando E/S mientras se está ejecutando otro. Haz algunas pruebas y ve lo que sucede.

+0

Suponiendo que no quiere hacer nada más con su máquina, por supuesto :-) – paxdiablo

+0

@paxdiablo - Por supuesto, aunque el sistema operativo dará tiempo de CPU a otros procesos. –

2

El número óptimo de hilos debe ser igual al número de núcleos, en cuyo caso la capacidad de cálculo de cada núcleo se utilizará por completo, si el cálculo en cada elemento es independiente.

11

Borealid's answer incluye prueba y descubra, que es imposible de superar como se aconseja.

Pero quizás haya más para probar esto de lo que pueda pensar: desea que sus hilos eviten la contención de datos siempre que sea posible. Si los datos son completamente de solo lectura, entonces es posible que vea el mejor rendimiento si sus hilos acceden a datos "similares", asegurándose de recorrer los datos en pequeños bloques a la vez, de modo que cada hilo acceda a los datos del same pages over and over again. Si los datos son completamente de solo lectura, entonces no hay problema si cada núcleo obtiene su propia copia de las líneas de caché. (Aunque esto podría no aprovechar al máximo el uso de la caché de cada núcleo.)

Si los datos son de ninguna manera modificada, a continuación, verá mejoras significativas en el rendimiento si mantiene los hilos distancia entre sí, por un montón .La mayoría de las memorias caché almacenan datos a lo largo de cache lines, y desea desesperadamente mantener cada cache line from bouncing among CPUs para un buen rendimiento. En ese caso, es posible que desee mantener los diferentes subprocesos ejecutándose en datos que están muy separados para evitar que se topen entre sí.

Entonces, si está actualizando los datos mientras trabajaba en él, le recomendaría tener N o 2 * N hilos de ejecución (para N núcleos), comenzando con TAMAÑO/N * M como punto de partida, para los hilos 0 a M. (0, 1000, 2000, 3000, para cuatro hilos y 4000 objetos de datos). Esto le dará la mejor oportunidad de alimentar diferentes líneas de caché para cada núcleo y permitir que las actualizaciones procedan sin rebotar en la línea de caché:

+--------------+---------------+--------------+---------------+--- ... 
| first thread | second thread | third thread | fourth thread | first ... 
+--------------+---------------+--------------+---------------+--- ... 

Si eres no actualización de los datos mientras se trabaja en ella, es posible que desee iniciar o N 2 * N hilos de ejecución (para n núcleos), comenzando con 0, 1, 2, 3 , etc. y moviendo cada uno hacia delante por N o 2 * N elementos con cada iteración. Esto permitirá que el sistema de caché busque cada página de la memoria una vez, llene las memorias caché de la CPU con datos casi idénticos, y esperemos que mantenga cada núcleo poblado con datos recientes.

+-----------------------------------------------------+ 
| 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 ... | 
+-----------------------------------------------------+ 

También recomiendo el uso de sched_setaffinity(2) directamente en el código de la fuerza los diferentes hilos a sus propios procesadores. En mi experiencia, Linux apunta al keep each thread on its original processor tanto que no migrará tareas a otros núcleos que de otro modo estarían inactivos.

+0

Muchas gracias por sus explicaciones. Acerca de la última frase: ¿Importa si estoy en Windows 7 o Windows Server 2008 R2? – Shredderroy

+0

@Shredderroy: es importante que 'sched_setaffinity (2)' sea Unix (¿o Linux?) Específico, en Windows será una función diferente. –

+0

@Shredderroy, Matthieu está en lo correcto; Windows puede hacer un mejor trabajo equilibrando tareas entre CPU que Linux de todos modos. Prueba de prueba :) – sarnold

1

Acepto los comentarios anteriores. Debe ejecutar pruebas para determinar qué número rinde el mejor rendimiento. Sin embargo, esto solo producirá el mejor rendimiento para el sistema en particular para el que está optimizando. En la mayoría de los escenarios, su programa se ejecutará en máquinas ajenas, en cuya arquitectura no debe hacer demasiadas suposiciones.

Una buena manera de determinar numéricamente el número de hilos para comenzar sería utilizar

std::thread::hardware_concurrency() 

Esto es parte de la C++ 11 y debe producir el número de núcleos lógicos en el sistema actual. Núcleos lógicos significa el número físico de núcleos, en caso de que el procesador no admita hilos de hardware (es decir, HyperThreading), o el número de subprocesos de hardware.

También hay una función Boost que hace lo mismo, consulte Programmatically find the number of cores on a machine.

0

El número óptimo de núcleos (hilos) probablemente estará determinado por cuando logra la saturación del sistema de memoria (cachés y RAM). Otro factor que podría entrar en juego es el bloqueo entre núcleos (bloqueando un área de memoria a la que otros núcleos pueden querer acceder, actualizándola y luego desbloqueándola) y qué tan eficiente es (cuánto tiempo está en funcionamiento el bloqueo y con qué frecuencia está bloqueado/desbloqueado).

Un núcleo único que ejecuta un software genérico cuyo código y datos no están optimizados para múltiples núcleos se acercará a saturar la memoria por sí solo. Agregar más núcleos, en tal escenario, dará como resultado una aplicación más lenta.

De modo que, a menos que su código se base en gran medida en los accesos a memoria, creo que la respuesta a su pregunta es una (1).

Cuestiones relacionadas