Por defecto, las variables son shared.
Cada thread utiliza su propia pila, por lo que las variables declaradas en la propia región paralela (o en una rutina) son privadas. R.P.: Cláusulas de ámbito
? firstprivate( ) Las variables privadas no están inicializadas al comienzo (ni dejan rastro al final).
Para poder pasar un valor a estas variables hay que declararlas firstprivate. R.P.: Cláusulas de ámbito
> Ejemplo: X = Y = Z = 0;
#pragma omp parallel private(Y) firstprivate(Z) { … X = Y = Z = 1; }
…
(Gp:) valores dentro de la región paralela? X = Y = Z =
(Gp:) valores fuera de la región paralela? X = Y = Z =
? (0) ? (0) 1 0 0 ? R.P.: Cláusulas de ámbito
? reduction( ) Las operaciones de reducción son típicas en muchas aplicaciones paralelas. Utilizan variables a las que acceden todos los procesos y sobre las que se efectúa alguna operación de “acumulación” en modo atómico (RMW).
Caso típico: la suma de los elementos de un vector.
Si se desea, el control de la operación puede dejarse en manos de OpenMP, declarando dichas variables de tipo reduction. R.P.: Cláusulas de ámbito
> Ejemplo: OJO: no se sabe en qué orden se va a ejecutar la operación –> debe ser conmutativa (cuidado con el redondeo).
#pragma omp parallel private(X) reduction(+:sum) {
X = … … sum = sum + X; … } (Gp:) La propia cláusula indica el operador de reducción a utilizar.
R.P.: Cláusulas de ámbito
? default (none / shared)
none: obliga a declarar explícitamente el ámbito de todas las variables. Útil para no olvidarse de declarar ninguna variable (da error al compilar).
shared: las variables sin “declarar” son shared (por defecto).
(En Fortran, también default(private): las variables sin declarar son privadas) R.P.: Cláusulas de ámbito
? Variables de tipo threadprivate Las cláusulas de ámbito sólo afectan a la extensión estática de la región paralela. Por tanto, una variable privada sólo lo es en la extensión estática (salvo que la pasemos como parámetro a una rutina). Si se quiere que una variable sea privada pero en toda la extensión dinámica de la región paralela, entonces hay que declararla mediante la directiva: #pragma omp threadprivate (X) R.P.: Cláusulas de ámbito
Las variables de tipo threadprivate deben ser “estáticas” o globales (declaradas fuera, antes, del main). Hay que especificar su naturaleza justo después de su declaración.
Las variables threadprivate no desaparecen al finalizar la región paralela (mientras no se cambie el número de threads); cuando se activa otra región paralela, siguen activas con el valor que tenían al finalizar la anterior región paralela. R.P.: Cláusulas de ámbito
? copyin(X) Declarada una variable como threadprivate, un thread no puede acceder a la copia threadprivate de otro thread (ya que es privada).
La cláusula copyin permite copiar en cada thread el valor de esa variable en el thread máster al comienzo de la región paralela. R.P.: Cláusulas de ámbito
? if (expresión) La región paralela sólo se ejecutará en paralelo si la expresión es distinta de 0.
Dado que paralelizar código implica costes añadidos (generación y sincronización de los threads), la cláusula permite decidir en ejecución si merece la pena la ejecución paralela según el tamaño de las tareas (por ejemplo, en función del tamaño de los vectores a procesar). R.P.: Otras cláusulas
? num_threads(expresión)
Indica el número de hilos que hay que utillizar en la región paralela.
Precedencia: vble. entorno >> función >> cláusula R.P.: Otras cláusulas
? Cláusulas de la región paralela
– shared, private, firstprivate(var) reduction(op:var) default(shared/none)
copyin(var)
– if (expresión) – num_threads(expresión) R.P.: Resumen cláusulas
? Paralelización de bucles. Los bucles son uno de los puntos de los que extraer paralelismo de manera “sencilla” (paralelismo de datos, domain decomposition, grano fino). Obviamente, la simple replicación de código no es suficiente. Por ejemplo,
Reparto de tareas #pragma omp parallel shared(A) private(i) { for (i=0; i<100; i++) A[i] = A[i] + 1; } ?
Reparto de tareas Tendríamos que hacer algo así: #pragma omp parallel shared(A) private(tid,nth, ini,fin,i) { tid = omp_get_thread_num(); nth = omp_get_num_threads();
ini = tid * 100 / nth; fin = (tid+1) * 100 / nth;
for (i=ini; i<100; i++) A[i] = A[i] + 1; … } (Gp:) 0..24 (Gp:) 25..49 (Gp:) 50..74 (Gp:) 75..99
(Gp:) barrera
(Gp:) ámbito variables reparto iteraciones sincronización
? Las directivas parallel y for pueden juntarse en
#pragma omp parallel for cuando la región paralela contiene únicamente un bucle. ? En todo caso, la decisión de paralelizar un bucle debe tomarse tras el correcto análisis de las dependencias. Reparto de tareas (for)
? Para facilitar la paralelización de un bucle, hay que aplicar todas las optimizaciones conocidas para la “eliminación” de dependencias:
— variables de inducción — reordenación de instrucciones — alineamiento de las dependencias — intercambio de bucles, etc. Reparto de tareas (for)
for (i=0; i export OMP_SCHEDULE=“dynamic,3” Cláusula schedule ? auto La elección de la planificación la realiza el compilador (o el runtime system). Es dependiente de la implementación.
RECUERDA: estático menos coste / mejor localidad datos dinámico más coste / carga más equilibrada Cláusula schedule Bajo ciertas condiciones, la asignación de las iteraciones a los threads se puede mantener para diferentes bucles de la misma región paralela.
Se permite a las implementaciones añadir nuevos métodos de planificación.
Cláusula collapse ? collapse(n) El compilador formará un único bucle y lo paralelizará. Se le debe indicar el número de bucles a colapsar #pragma omp parallel for collapse(2) for (i=0; i1000) for (i=0; i Ejemplo:
? Igual que en el caso del pragma for, si la región paralela sólo tiene secciones, pueden juntarse ambos pragmas en uno solo:
#pragma omp parallel sections [cláusulas]
(cláusulas suma de ambos pragmas) Rep. de tareas (sections)
2 Directiva single
Define un bloque básico de código, dentro de una región paralela, que no debe ser replicado; es decir, que debe ser ejecutado por un único thread. (por ejemplo, una operación de entrada/salida).
No se especifica qué thread ejecutará la tarea. Rep. de tareas (single)
? La salida del bloque single lleva implícita una barrera de sincronización de todos los threads. La sintaxis es similar a las anteriores:
#pragma omp single [cláus.]
Cláusulas: (first)private, nowait
copyprivate(X)
Para pasar al resto de threads (BC) el valor de una variable threadprivate, modificada dentro del bloque single. Rep. de tareas (single)
#pragma omp parallel { … ; #pragma omp single inicializar(A);
#pragma omp for for(i=0; i Ejemplo:
? Comentarios finales El reparto de tareas de la región paralela debe hacerse en base a bloques básicos; además, todos los threads deben alcanzar la directiva. Es decir: — si un thread llega a una directiva de reparto, deben llegar todos los threads. — una directiva de reparto puede no ejecutarse, si no llega ningún thread a ella. — si hay más de una directiva de reparto, todos los threads deben ejecutarlas en el mismo orden. — las directivas de reparto no se pueden anidar. Reparto de tareas
? Las directivas de reparto pueden ir tanto en el ámbito lexicográfico (estático) de la región paralela como en el dinámico. Por ejemplo: int X; #pragma omp threadprivate(X) … #pragma omp parallel { X = … init(); … } (Gp:) void init() { #pragma omp for for (i=0; i export OMP_NESTED=TRUE
Una función devuelve el estado de dicha opción: omp_get_nested(); (true o false) Reg. paralelas anidadas
Reg. paralelas anidadas Master Región paralela externa Región paralela anidada Región paralela externa Se pueden anidar regiones paralelas con cualquier nivel de profundidad.
Reg. paralelas anidadas ? OpenMP 3.0 mejora el soporte al paralelismo anidado:
– La función omp_set_num_threads() puede ser invocada dentro de una región paralela para controlar el grado del siguiente nivel de paralelismo.
– Permite conocer el nivel de anidamiento mediante omp_get_level() y omp_get_active_level().
– Se puede acceder al tid del padre de nivel n omp_get_ancestor_thread_num(n)y al número de threads en dicho nivel.
Reg. paralelas anidadas ? OpenMP 3.0 mejora el soporte al paralelismo anidado:
– Permite controlar el máximo número de regiones paralelas anidadas activas mediante funciones y variables de entorno.
omp_get/set_max_active_levels();
> export OMP_MAX_ACTIVE_LEVELS=n
– Permite controlar el máximo número de threads mediante funciones y variables de entorno omp_get_thread_limit(); > export OMP_THREAD_LIMIT=n
? Puede usarse la cláusula if para decidir en tiempo de ejecución si paralelizar o no. Para saber si se está ejecutando en serie o en paralelo, se puede usar la función: omp_in_parallel(); ? Puede obtenerse el número de procesadores disponibles mediante omp_get_num_proc() ;
y utilizar ese parámetro para definir el número de threads. Algunas funciones más
? Puede hacerse que el número de threads sea dinámico, en función del número de procesadores disponibles en cada instante:
> export OMP_DYNAMIC=TRUE/FALSE omp_set_dynamic(1/0);
Para saber si el número de threads se controla dinámicamente:
omp_get_dynamic(); Algunas funciones más
Algunas funciones más ? Se han añadido algunas variables de entorno para gestionar el tamaño de la pila:
> export OMP_STACKSIZE tamaño [B|K|M|G]
y para gestionar la política de espera en la sincronización:
> export OMP_WAIT_POLICY [ACTIVE|PASSIVE]
Página anterior | Volver al principio del trabajo | Página siguiente |