Sei sulla pagina 1di 39

OpenMP: API para escribir

aplicaciones SMP portátiles

Fernando Félix-Redondo
Universidad Carlos III
Departamento de Informática
Índice
 Introducción: OpenMP a vista de pájaro
 Sintaxis básica: Empezando con OpenMP
 “Constructores” de OpenMP:
 Regiones paralelas
 Trabajo compartido (Worksharing)
 Entorno de datos
 Sincronización
 Funciones en tiempo de ejecución y variables de entorno

 Evitando errores de programación SMP


 Compiladores OpenMP
OpenMP a vista de pájaro
 Para programar sistemas con
memoria distribuida:
 Hay que usar “bibliotecas de
funciones” para paso de
mensajes como MPI o PVM

 Para los sistemas de memoria Shared Memory

compartida:
 Es posible también usar paso de
mensajes pero …
 Existen soluciones basadas
directamente en el paradigma de
memoria compartida. Ejemplo:
OpenMP
OpenMP a vista de pájaro
 OpenMP es un API para OpenMP
escribir aplicaciones multi-
hilos (multithreaded)
 Conjunto de directivas de
Funciones de
compilación, Directivas
de compilación
Librería en tiempo
Variables
de Entorno
de ejecución
 algunas funciones de
biblioteca y
 unas pocas variables de
entorno #pragma omp parallel for

 OpenMP es básicamente lo omp_get_thread_num()


mismo en C/C++ y en
Fortran OMP_NUM_THREADS
OpenMP a vista de pájaro
 Modelo de programación denominado “Paralelismo Fork-Join”
 El hilo maestro arranca un conjunto de hilos cuando se necesita
paralelizar una determinada tarea
 Al terminar su labor, los hilos creados terminan y continua la ejecución
secuencial
 Generalmente, el paralelismo se añade de forma incremental a un
programa secuencial pre-existente
OpenMP a vista de pájaro
 OpenMP se usa habitualmente
para paralelizar bucles double V[10000];

 Se localizan los bucles que for (int i=0; i< 10000; i++) {
consumen más tiempo
calculo_brutal( V[i] );
 Se reparten entre los distintos }
hilos o threads
 Hay que evitar las condiciones
de carrera entre hilos
 La compartición de datos de
forma no intencionada puede
dar problemas double V[10000];
 Se puede usar sincronización #pragma omp parallel for
para evitar conflictos de datos for (int i=0; i< 10000; i++) {
 La sincronización es “cara” calculo_brutal( V[i] );
por lo que… }
 …hay que distribuir los datos
entre los hilos con inteligencia
Índice
 Introducción: OpenMP a vista de pájaro
 Sintaxis básica: Empezando con OpenMP
 “Constructores” de OpenMP:
 Regiones paralelas
 Trabajo compartido (Worksharing)
 Entorno de datos
 Sincronización
 Funciones en tiempo de ejecución y variables de entorno

 Evitando errores de programación SMP


 Compiladores OpenMP
Sintaxis básica: Empezando con OpenMP
 Como primer paso hay que incluir el fichero de
cabeceras al principio del programa en C/C++:
#include <iostream>
#include <omp.h>

using namespace std;

int main(int argc, char* argv[])


{
omp_set_num_threads(10); // Número de Hilos

#pragma omp parallel // Ejecuta en paralelo a partir de aquí


cout << "Hello World!!" << endl;

// Aquí de nuevo es secuencial la ejecución


return 0;
}
Sintaxis básica: Constructores
 Los “constructores” de OpenMP son básicamente
directivas de compilación (pragmas)

#pragma omp construct [clause [clause]…]

 Un compilador NO-OpenMP simplemente ignorará las


“pragmas”
 Con OpenMP, es posible que el mismo código fuente
sirva para la versión secuencial y paralela de un
programa
Sintaxis básica: Compilación condicional
#include <omp.h>
#include <iostream>
using namespace std;

int main(int argc, char* argv[])


{
int nthreads=1; Si se usa
OpenMP, estará
#pragma omp parallel // Ejecuta en paralelo a partir de aquí
definida
{
cout << "Hello World!!" << endl;

#ifdef _OPENMP
nthreads=omp_get_num_threads(); // Averiguar el número de Hilos
#endif
cout << "Somos " << nthreads << " threads" << endl;
}
// Aquí de nuevo es secuencial la ejecución
return 0;
}
Sintaxis básica: Bloques estructurados
double V[10000], Z[10000];
 La mayoría de los #pragma omp parallel for
constructores OpenMP
for (int i=0; i< 10000; i++) {
se aplican sobre bloques calculo_brutal( V[i] );
estructurados Z[i]=func(i)*sqrt(V[i]
}

 Bloque estructurado:
Bloque de código con un double V[10000], Z[10000];
único punto de entrada al #pragma omp parallel for
principio y un único punto for (int i=0; i< 10000; i++) {
de salida al final; la única calculo_brutal( V[i] );
if (cond(V[i]))
excepción es exit()
goto salida;
Z[i]=func(i)*sqrt(V[i]
}
Índice
 Introducción: OpenMP a vista de pájaro
 Sintaxis básica: Empezando con OpenMP
 “Constructores” de OpenMP:
 Regiones paralelas
 Trabajo compartido (Worksharing)
 Entorno de datos
 Sincronización
 Funciones en tiempo de ejecución y variables de entorno

 Evitando errores de programación SMP


 Compiladores OpenMP
Constructores OpenMP

 Tenemos 5 categorías de constructores OpenMP:

 Regiones paralelas

 Trabajo compartido (worksharing)

 Entorno de datos

 Sincronización

 Funciones “runtime” y variables de entorno


Regiones paralelas
 La expresión “regiones paralelas” se podría
traducir, simplemente, como “hilos” (threads)

 Este constructor permite crear un conjunto de


hilos (threads) en OpenMP
double V[10000];
omp_set_num_threads(4);
Cada thread ejecutará el
#pragma omp parallel
código perteneciente al
{
bloque y llama a “hazalgo”
int ID =omp_thread_num();
con un ID diferente hazalgo(ID,V);
}
printf(“done”);
Regiones paralelas double V[10000];
omp_set_num_threads(4);

#pragma omp parallel


{
double V[10000] int ID =omp_thread_num();
boo(ID,V);
}
omp_set_num_threads(4) printf(“done”);

hazalgo(0,V) hazalgo(1,V) hazalgo(2,V) hazalgo(3,V)

printf(“done”)
Una sola copia de
V es compartida
por todos los hilos
Los threads esperan aquí
a que todos hayan
terminado (sinc. barrera)
Regiones paralelas: Algunos detalles
 Modo dinámico (por defecto):
 El número de hilos puede cambiar de una región paralela
a otra
 Fijarel número de hilos, solo establece el número máximo
de hilos posible – podrían obtenerse menos
 Modo estático:
 El número de hilos es fijo y controlado por el programador
 OpenMP permite anidar regiones paralelas, pero…
 El compilador puede decidir serializar la región anidada
Trabajo compartido (Worksharing)
 El constructor “for” permite dividir las iteraciones
de un bucle entre diferentes hilos

 Por defecto hay una barrera al final del “omp for”


Se puede usar la cláusula “nowait” para
desactivarla
double V[10000];
#pragma omp parallel
#pragma omp for
for (int i=0; i< 10000; i++) {
calculo( V[i] );
}
Trabajo compartido: Ejemplo ilustrativo
for (i=0;i<N;i++) { a[i]=a[i]+b[i]; }

#pragma omp parallel


{
int id, i, Nthrds, istart, iend;
id=omp_get_thread_num();
Nthrds=omp_get_num_threads();
istart = id*N/Nthrds;
iend = (id+1)*N/Nthrds;
for(i=istart; i<iend;i++) { a[i]=a[i]+b[i]; }
}

#pragma omp parallel


#pragma omp for schedule(static)
for (i=0;i<N;i++) { a[i]=a[i]+b[i]; }
Trabajo compartido: Cláusula schedule
 Esta cláusula permite controlar como repartimos las
iteraciones del bucle entre los threads
 schedule(static [,#n])
 Reparte bloques de iteraciones de tamaño “#n” a cada hilo.
 schedule(dynamic [,#n])
 Cada hilo toma “#n” iteraciones de una cola hasta que todas se han
acabado.
 schedule(guided[,#n])
 Los hilos dinámicamente toman bloques de iteraciones. El tamaño
del bloque al principio es grande y disminuye hasta “#n” a medida
que progresa la ejecución.
 schedule(runtime)
 La política schedule y el tamaño de bloque se toma de la variable
de entorno OMP_SCHEDULE.
 (Ejemplo: export OMP_SCHEDULE=“dynamic,10”)
Trabajo compartido: cláusula if

 Si las iteraciones no suponen


double V[10000];
“suficiente trabajo” no tiene sentido
ejecutarlas en paralelo #pragma omp parallel for \
if (N> 1000)
 Interesa que la decisión sobre si se
paraleliza o no se tome en tiempo de for (int i=0; i< N; i++) {
ejecución calculo(V,i);
}
 Con “if” el bucle se ejecutará en
paralelo si la condición “cond” se
evalúa como VERDAD
El compilador incluye
 Si no, el bucle se ejecuta
por sí mismo el código
secuencialmente, sin incurrir en necesario para implementar
ninguna penalización por overhead. la sentencia condicional
Trabajo compartido: Anidamiento de bucles
 Cuando uno de los bucles del anidamiento tiene
una cláusula parallel for, ésta solo aplica al que
le sigue inmediatamente
int N=1000; int N=1000;
int M=1000; int M=1000;
#pragma omp parallel for for (int i=0; i< N; i++) {
for (int i=0; i< N; i++) {
// Some operations here
// Some operations here
#pragma omp parallel for
for (int j=0;j<M;j++) { for (int j=0;j<M;j++) {
// Additional stuff // Additional stuff
} }
} }
Restricciones a los bucles paralelos
 OpenMP establece algunas restricciones sobre
los bucles que pueden ser paralelizados
 Debe ser posible conocer el número de iteraciones en tiempo de
ejecución antes de comenzarlo
 El índice debe ser un integer
 El incremento debe ser siempre igual
 No se puede usar break o goto para salir del bucle; sí es posible
usar goto dentro del propio bucle
 Es posible usar continue
 Si se generan excepciones en C++, éstas deben ser tratadas
dentro del bloque del bucle
Trabajo compartido: constructor sections
 Permite paralelizar una #pragma omp parallel sections
secuencia de tareas sin {
dependencias de datos entre X_calculo();
ellas #pragma omp section
Y_calculo();
 Asignamos cada tarea a un hilo #pragma omp section
diferente Z_calculo();
}
 Es útil cuando resulta difícil
paralelizar cada una de las Barrera implícita
tareas individualmente
{

X_calculo Y_calculo Z_calculo

} Barrera impícita
Trabajo compartido: constructor single
 En ocasiones las regiones
paralelas contienen tareas
que deberían ser ejecutadas
#pragma omp parallel
por un único hilo { Barrera implícita
 Un ejemplo típico lo constituye #pragma omp single
la entrada/salida dentro de { leer_datos(X); }
una región paralela que debe
calcular(X);
ejecutarse secuencialmente calcular_mas()
 El constructor single también
incluye una barrera al final del #pragma omp single nowait
{ escribir_resultado(X); }
bloque (a no ser que se use
}
nowait)

Aquí no hay barrera


Entorno de datos

 Modelo de programación de Memoria Compartida


 La mayoría de las variables están compartidas por defecto

 Las variables globales son compartidas entre los


threads
 En C: Variables estáticas (static) o globales cuyo ámbito es el
fichero

 Pero no todo está compartido…


 Las variables de pila en subprogramas llamados desde las
regiones paralelas son PRIVADAS
 Las variables automáticas dentro de un bloque son PRIVADAS
Controlar la compartición de datos
 Es fundamental que los hilos puedan también
usar variables privadas.
 OpenMP tiene cláusulas para controlar el
ámbito de los datos dentro de los constructores
paralelos int N=1000;
 shared int M=1000;
#pragma omp parallel for
 private for (int i=0; i< N; i++) {
 firstprivate y lastprivate a=a+10;
 default
for (int j=0;j<M;j++) {
 reduction // Additional stuff
}
}
Entorno de datos: cláusula shared
 Establece que las variables
especificadas serán compartidas
por todos los hilos.
 Sólo existe una instancia de cada
una de esas variables

 Cualquier modificación por parte


de uno de los hilos, actualizará
la instancia compartida.
 Cualquier cambio sobre la variable
de uno de los hilos es visto por los
demás
Entorno de datos: cláusula private
 Crea un copia “privada” de la variable para cada
hilo
 La variable “privada” no es inicializada y tiene un
valor indefinido a la entrada y a la salida

int N=1000;
int a=1;

#pragma omp parallel for private (a)


for (int i=0; i< N; i++) {

a=a+10;

}
cout << “El valor de a” << a << endl;
Entorno de datos: Por defecto…
 Todas las variables usadas dentro de un
constructor paralelo son tratadas como
“compartidas”
 Es el comportamiento deseado generalmente para
variables de “sólo lectura” dentro del bucle paralelo
 Si son modificadas, hay que declararlas privadas o
sincronizar el acceso a las mismas
Entorno de datos: Por defecto…(II)

Los índices de los bucles paralelos son


identificados como tal y declarados privados de
forma automática por el compilador; pero…

…los índices de los bucles secuenciales


dentro de secciones paralelas en C/C++ siguen
siendo compartidos (shared).
Ejemplos
Aquí i es declarada
int N=1000; automáticamente como
int i,a=1; variable privada por el
int x[1000]; compilador

#pragma omp parallel for


for (i=0; i< N; i++) {
x[i]=i+10;
} Ejemplo 1

int i,a=1,N=1000;
Esta i, sin embargo, es
#pragma omp parallel
compartida por todos los
{
hilos. En este caso
int x[1000];
tendríamos problemas
for (i=0; i< N; i++) {
x[i]=i+10;
}
Ejemplo 2
}
Entorno de datos: reduction
 La reducción de datos es un tipo de cálculo muy
frecuente
 Consiste en aplicar repetidamente un operador binario (+,-,x,…)
sobre una determinada variable
 El compilador y el entorno de ejecución implementan este
cálculo de la forma más eficiente posible
 Se usa la cláusula: reduction (oper : lista_var)
Operadores para cláusula int N=1000;
reduction
int a=1;
+ SUMA

- RESTA
#pragma omp parallel for reduction ( + : a)
* PRODUCTO
for (i=0; i< N; i++)
a=+10;

cout << “El valor de a es: “ << a << endl;


Sincronización
 OpenMP tiene los siguientes constructores para soportar
operaciones de sincronización:
 critical section
 Sólo un thread a la vez puede entrar en una sección crítica
 atomic
 Es un caso especial del constructor anterior que se usa para ciertas
sentencias simples
 barrier
 Cada thread espera a que todos los threads lleguen
 flush
 Supone un punto en la ejecución donde un thread intenta crear un
visión consistente de la memoria
 ordered
 Fuerza a que el bloque se ejecute en orden secuencial
Funciones runtime y variables de entorno
 Rutinas para manejar locks
 omp_init_lock(), omp_set_lock()
 omp_unset_lock(), omp_test_lock()
 Rutinas para modificar/comprobar el número de hilos
 omp_set_num_threads(), omp_get_num_threads()
 omp_get_thread_num(), omp_get_max_threads()
 Encender/Apagar anidamiento y modo dinámico
 omp_set_nested(), omp_set_dynamic()
 omp_get_nested, omp_get_dynamic()
 ¿Estoy en una región paralela?
 omp_in_parallel()
 ¿Cuántos procesadores hay en el sistema?
 omp_num_procs()
Funciones runtime y variables de entorno

Variable Ejemplo Descripción

Especifica el tipo de planificación


OMP_SCHEDULE “dynamic,4” para los bucles paralelos
Especifica el número de hilos a
OMP_NUM_THREADS 16 usar durante la ejecución
Habilita/deshabilita el ajuste
OMP_DYNAMIC TRUE o FALSE dinámico de hilos
Habilita/deshabilita el paralelismo
OMP_NESTED TRUE o FALSE anidado

Variables de entorno
Índice
 Introducción: OpenMP a vista de pájaro
 Sintaxis básica: Empezando con OpenMP
 “Constructores” de OpenMP:
 Regiones paralelas
 Trabajo compartido (Worksharing)
 Entorno de datos
 Sincronización
 Funciones en tiempo de ejecución y variables de entorno

 Evitando errores de programación SMP


 Compiladores OpenMP
Índice
 Introducción: OpenMP a vista de pájaro
 Sintaxis básica: Empezando con OpenMP
 “Constructores” de OpenMP:
 Regiones paralelas
 Trabajo compartido (Worksharing)
 Entorno de datos
 Sincronización
 Funciones en tiempo de ejecución y variables de entorno

 Evitando errores de programación SMP


 Compiladores OpenMP
Compiladores OpenMP

 Intel C/C++ compiler for Linux


 Visual Studio 2005 y posteriores
 GCC versión 4.x
 SunStudio 12.x
Más información sobre OpenMP
 Bibliografía  Internet
 Chandra et al.; Parallel programming • Matsson, T & Eigenmann, R;
in OpenMP; Morgan-Kaufman “OpenMP: An API for writing
 Chapman et al.; Using OpenMP; MIT portable SMP application software”;
Press Tutorial at Supercomputing ‘99
 Quinn, M.; Parallel Programming in C
with MPI and OpenMP; McGraw-Hill

Potrebbero piacerti anche