Considere el siguiente programa de computadora muy simple:
for i = 1 to n:
y[i] = x[p[i]]
Aquí e y son matrices de n elementos de bytes, y p es una matriz de palabras de n elementos. Aquí n es grande, por ejemplo, n = 2 31 (de modo que solo una fracción insignificante de los datos cabe en cualquier tipo de memoria caché).
Suponga que consiste en números aleatorios , distribuidos uniformemente entre 1 y n .
Desde la perspectiva del hardware moderno, esto debería significar lo siguiente:
- leer es barato (lectura secuencial)
- leer es muy costoso (lecturas aleatorias; casi todas las lecturas son errores de caché; tendremos que buscar cada byte individual de la memoria principal)
- escribir es barato (escritura secuencial).
Y esto es de hecho lo que estoy observando. El programa es muy lento en comparación con un programa que solo realiza lecturas y escrituras secuenciales. Excelente.
Ahora viene la pregunta: ¿qué tan bien se paraleliza este programa en las plataformas modernas de múltiples núcleos?
Mi hipótesis era que este programa no se paraleliza bien. Después de todo, el cuello de botella es la memoria principal. Un solo núcleo ya está perdiendo la mayor parte de su tiempo solo esperando algunos datos de la memoria principal.
Sin embargo, esto no fue lo que observé cuando comencé a experimentar con algunos algoritmos donde el cuello de botella era este tipo de operación.
Simplemente reemplacé el bucle for ingenuo con un bucle for paralelo paralelo OpenMP (en esencia, dividirá el rango en partes más pequeñas y ejecutará estas partes en diferentes núcleos de CPU en paralelo).
En las computadoras de gama baja, las aceleraciones fueron menores. Pero en las plataformas de gama alta me sorprendió que estaba obteniendo excelentes aceleraciones casi lineales. Algunos ejemplos concretos (los tiempos exactos pueden estar un poco apagados, hay muchas variaciones aleatorias; estos fueron solo experimentos rápidos):
2 x Xeon de 4 núcleos (en total 8 núcleos): factoriza 5-8 aceleraciones en comparación con la versión de un solo hilo.
2 x Xeon de 6 núcleos (en total 12 núcleos): factoriza entre 8 y 14 aceleraciones en comparación con la versión de un solo hilo.
Ahora esto fue totalmente inesperado. Preguntas:
Precisamente, ¿por qué este tipo de programa es tan paralelo ? ¿Qué pasa en el hardware? (Mi conjetura actual es algo así: las lecturas aleatorias de diferentes hilos están "canalizadas" y la tasa promedio de obtener respuestas a estas es mucho mayor que en el caso de un solo hilo).
¿Es necesario usar múltiples hilos y múltiples núcleos para obtener aceleraciones? Si realmente se lleva a cabo algún tipo de interconexión en la interfaz entre la memoria principal y la CPU, una aplicación de un solo subproceso no podría hacerle saber a la memoria principal que pronto necesitará , x [ p [ i + 1 ] ] , ... ¿y la computadora podría comenzar a buscar las líneas de caché relevantes de la memoria principal? Si esto es posible en principio, ¿cómo lo logro en la práctica?
¿Cuál es el modelo teórico correcto que podríamos usar para analizar este tipo de programas (y hacer predicciones correctas del rendimiento)?
Editar: ahora hay algunos códigos fuente y resultados de referencia disponibles aquí: https://github.com/suomela/parallel-random-read
Algunos ejemplos de figuras de estadio ( ):
- aprox. 42 ns por iteración (lectura aleatoria) con un solo hilo
- aprox. 5 ns por iteración (lectura aleatoria) con 12 núcleos.