MEASUREMENT SCALE
RESEARCH METHOD
Esta Sección muestra cómo mejorar el rendimiento de la paralelización de los tres códigos Stencil que utilizamos en esta Tesis. Para ello evaluamos dos ca- racterísticas fundamentales en la paralelización de un código. En primer lugar, la planificación de la carga de trabajo asignada a cada uno de los hilos ejecuta- dos; esto es, cómo distribuir las diferentes iteraciones del bucle del cálculo del
kernel Stencil entre los hilos del Xeon Phi. En segundo lugar comprobamos la
afinidad para ver cómo asignar los diversos hiloslógicosa los núcleos físicosde la arquitectura. Debido al número de hilos y de núcleos que presenta Xeon Phi, se
4.2. Mejorar la paralelización recomienda evaluar ambas características para obtener un mayor rendimiento en la ejecución de cualquier código paralelo.
4.2.1
Políticas de planificación de bucles O
PENMP
Uno de los atributos más importantes para el rendimiento de las aplicaciones para- lelas que se están ejecutando usando varios hilos es asegurar el balanceo de carga, es decir, que la división del trabajo entre los hilos es equitativa. El balanceo de la carga de trabajo en los diversos hilos es extremadamente importante, ya que garantiza que los procesadores físicos (los núcleos) están ocupados de forma similar, obteniendo por tanto el mejor rendimiento posible. Sin una carga equili- brada, algunos hilos pueden terminar significativamente antes que los demás, dejando inactivos algunos de los núcleos del procesador, y desperdiciando así oportunidades para mejorar el rendimiento.
Por lo general, mediante el examen del código fuente es fácil determinar la variabilidad en el tiempo de cálculo de las diversas iteraciones de un bucle. En muchos casos, las diversas iteraciones del bucle consumen una cantidad similar de tiempo, por lo que no hace falta realizar ninguna planificación para distribuir la carga de trabajo entre los hilos que van a ejecutar dicho bucle (por defecto, se realiza una distribución uniforme). Cuando esto no es cierto, es necesario ajustar el balanceo de la carga dando información adicional en la programación del bucle en OpenMP para distribuir mejor las iteraciones del bucle entre los hilos (y por tanto entre los núcleos).
Esta información adicional es lo que se conoce como política de planificación de un bucle, y consiste en dar la información necesaria para que en la ejecución del código se puedan distribuir las iteraciones del bucle entre los hilos en cantidades aproximadamente iguales. Además, las diversas políticas de planificación tratan también de reducir al mínimo los posibles conflictos de memoria que pueden surgir debido al problema de la falsa compartición (false sharing) de bloques de memoria. A veces, puede suceder que la mejor opción para resolver los problemas de memoria, puede ser malo para el balanceo de carga entre los hilos, y al contrario. Por ello, se deben evaluar las diversas políticas de planificación para encontrar un equilibrio entre el uso óptimo de la memoria y el balance de la carga óptima mediante la medición del rendimiento para ver qué política produce los mejores resultados.
En el lenguaje OpenMP hay que agregar la clausulascheduleal pragmaque paraleliza un bucle para indicarle a OpenMP qué política se quiere aplicar a la hora de asignar trabajo a cada hilo de ejecución. Por tanto, la directiva queda de
la siguiente forma:#pragma omp parallel for schedule(tipo [,tamaño1]). Se pueden proporcionar cuatro tipos de planificación más uno adicional que permite en tiempo de ejecución elegir entre uno de estos cuatro tipos. A continuación describimos cada uno de estas políticas de planificación:
1. static: Divide el bucle en trozos de igual tamaño o tan igual como sea
posible en el caso en el que el número de iteraciones del bucle no sea divisible por el número de hilos multiplicado por el tamaño especificado. Por defecto, el tamaño de iteraciones asignado a cada hilo es igual a:Número
de iteraciones / Número de hilos. Si se pone un tamaño de 1, se intercalan las
iteraciones entre los hilos disponibles.
2. dynamic: Usa una cola de trabajo interna para dar un bloque de iteraciones
del bucle a cada hilo en tiempo de ejecución. Cuando un hilo termina, recupera el siguiente bloque de iteraciones del bucle de la cola de trabajo. Por defecto, el tamaño es 1 (se va asignando iteración a iteración), aunque se puede modificar dicho valor cambiando el tamaño en la directiva. Hay que tener cuidado al usar este tipo de planificación debido a la sobrecarga adicional que tiene en tiempo de ejecución.
3. guided: Similar a la planificación dinámica, pero con un tamaño de carga de
trabajo (número de iteraciones) que comienza grande y va disminuyendo para lograr un mejor equilibrio entre la carga de los hilos. El parámetro opcional de tamañoespecifica el tamaño de carga de trabajo más pequeña a considerar. Por defecto, dicho tamaño mínimo es igual a: Número de
iteraciones / Número de hilos.
4. auto: Cuando se especifica una planificación automática, la decisión con respecto a la planificación se delega en el compilador. El programador cede al compilador la libertad de elegir cualquier posible asignación en el número de iteraciones para cada hilo.
5. runtime: En este caso, se utiliza la variable de entornoOMP_SCHEDULEpara
especificar cuál de los tres primeros tipos de planificación debe ser utilizado. La variableOMP_SCHEDULEes una cadena con el mismo formato que aparece en la directiva de asignación de la planificación.
4.2. Mejorar la paralelización En el momento de la evaluación de dichas políticas, hemos probado con unos tamaños de 1, 2 y 4 para las diferentes políticas de planificación, mas el tamaño por defecto que tiene cada una de dichas estrategias.
4.2.2
Políticas de afinidad
La librería de ejecución o (runtime) que tiene OpenMP posee la capacidad de asignar los hilos lógicosa diferentes núcleos físicos de nuestra arquitectura. De- pendiendo de la topología de la conexión entre los núcleos, de las características de comunicación de la aplicación, y del sistema operativo, la afinidad puede producir un cierto beneficio en el rendimiento de una aplicación. Para activar un tipo u otro de afinidad, se usa la variable de entorno KMP_AFFINITY. En Intel Xeon Phi tenemos 3 tipos de afinidad:
• compact: Con este tipo de afinidad el runtime de OpenMP asigna el hilo
<n>+1 al núcleo más cercano (o el mismo núcleo si es posible) donde se
asignó el hilo<n>. Por ejemplo, con este tipo de afinidad tendríamos que el núcleo físico 0 estaría ejecutando los hilos 0..3, en el núcleo físico 1 estarían los hilos 4..7, y así sucesivamente.
• scatter: Con este tipo de afinidad la libreríaruntime de OpenMP asigna
los hilos de la manera más dispersa posible. Por ejemplo, con este tipo de afinidad tendríamos que en el núcleo físico 0 estaría el hilo 0, en el núcleo físico 1 estaría el hilo 1, y así sucesivamente. La afinidad scatter es la contraria a la afinidadcompact.
• balanced: Este tipo de afinidad es exclusivo de la arquitectura Intel Xeon
Phi. Con este tipo de afinidad el runtime de OpenMP asigna los hilos a diferentes núcleos hasta que todos los núcleos tengan al menos 1 hilo. En esto es similar a cómo funciona la afinidadscatter. Sin embargo, cuando
elruntime tiene que asignar varios hilos a un núcleo, la afinidadbalanced
asegura que los hilos con numeración más parecida estarán en el mismo núcleo, cosa que no lo asegura la afinidad scatter.
En la Figura 4.1 se muestra un ejemplo de cada tipo de afinidad para una configuración de 8 hilos y 4 núcleos.
Para probar el efecto de la afinidad, en nuestros experimentos hemos modifi- cado la variable de entorno KMP_AFFINITYacompact, balanced, o scatter.
Figura 4.1: Distribuciones de tipo de afinidad (KMP_AFFINITY).