Paralelismo de bucles en OpenMP
Parámetros
Cláusula | Parámetro |
---|---|
privado |
Lista separada por comas de variables privadas |
primeroprivado |
Como private , pero inicializado al valor de la variable antes de entrar en el bucle |
últimoprivado |
Como private , pero la variable obtendrá el valor correspondiente a la última iteración del ciclo al salir |
reducción |
operador de reducción : lista separada por comas de las variables de reducción correspondientes |
horario |
static , dynamic , guided , auto o runtime con un tamaño de fragmento opcional después de una coma para los 3 primeros |
colapso |
Número de bucles perfectamente anidados para colapsar y paralelizar juntos |
ordenado |
Indica que algunas partes del ciclo deberán mantenerse en orden (estas partes se identificarán específicamente con algunas cláusulas “ordenadas” dentro del cuerpo del ciclo) |
sin esperar |
Eliminar la barrera implícita existente por defecto al final de la construcción del bucle |
El significado de la cláusula schedule
es el siguiente:
static[,chunk]
: distribuye estáticamente (lo que significa que la distribución se realiza antes de entrar en el bucle) las iteraciones del bucle en lotes de tamañochunk
de forma rotativa. Si no se especificachunk
, entonces los fragmentos son lo más parejos posible y cada subproceso obtiene como máximo uno de ellos.dynamic[,chunk]
: distribuya las iteraciones de bucle entre los subprocesos por lotes de tamañochunk
con una política de orden de llegada, hasta que no quede ningún lote. Si no se especifica,chunk
se establece en 1guided[,chunk]
: comodynamic
pero con lotes cuyos tamaños son cada vez más pequeños, hasta 1auto
: Deje que el compilador y/o la biblioteca de tiempo de ejecución decidan qué es lo más adecuadoruntime
: Aplazar la decisión en tiempo de ejecución mediante la variable de entornoOMP_SCHEDULE
. Si en tiempo de ejecución la variable de entorno no está definida, se utilizará la programación predeterminada
El valor predeterminado para programación
es definición de implementación. En muchos entornos es ’estático’, pero también puede ser ‘dinámico’ o muy bien podría ser ‘automático’. Por lo tanto, tenga cuidado de que su implementación no dependa implícitamente de él sin configurarlo explícitamente.
En los ejemplos anteriores, usamos la forma fusionada parallel for
o parallel do
. Sin embargo, la construcción loop se puede usar sin fusionarla con la directiva parallel
, en la forma de una directiva independiente #pragma omp for [...]
o !$omp do [...]
dentro de un región ‘paralela’.
Solo para la versión de Fortran, las variables de índice de bucle de los bucles paralizados son siempre “privadas” de forma predeterminada. Por lo tanto, no hay necesidad de declararlos explícitamente privados
(aunque hacerlo no es un error).
Para la versión C y C++, los índices de bucle son como cualquier otra variable. Por lo tanto, si su alcance se extiende fuera de los bucles paralelizados (es decir, si no se declaran como for (int i = ...)
sino como int i; ... for (i = ...) )
entonces tienen que ser declarados privados
.
Ejemplo típico en C
#include <stdio.h>
#include <math.h>
#include <omp.h>
#define N 1000000
int main() {
double sum = 0;
double tbegin = omp_get_wtime();
#pragma omp parallel for reduction( +: sum )
for ( int i = 0; i < N; i++ ) {
sum += cos( i );
}
double wtime = omp_get_wtime() - tbegin;
printf( "Computing %d cosines and summing them with %d threads took %fs\n",
N, omp_get_max_threads(), wtime );
return sum;
}
En este ejemplo, solo calculamos 1 millón de cosenos y sumamos sus valores en paralelo. También cronometramos la ejecución para ver si la paralelización tiene algún efecto en el rendimiento. Finalmente, dado que medimos el tiempo, debemos asegurarnos de que el compilador no optimice el trabajo que hemos realizado, por lo que pretendemos usar el resultado simplemente devolviéndolo.
Mismo ejemplo en Fortran
program typical_loop
use omp_lib
implicit none
integer, parameter :: N = 1000000, kd = kind( 1.d0 )
real( kind = kd ) :: sum, tbegin, wtime
integer :: i
sum = 0
tbegin = omp_get_wtime()
!$omp parallel do reduction( +: sum )
do i = 1, N
sum = sum + cos( 1.d0 * i )
end do
!$omp end parallel do
wtime = omp_get_wtime() - tbegin
print "( 'Computing ', i7, ' cosines and summing them with ', i2, &
& ' threads took ', f6.4,'s' )", N, omp_get_max_threads(), wtime
if ( sum > N ) then
print *, "we only pretend using sum"
end if
end program typical_loop
Aquí nuevamente calculamos y acumulamos 1 millón de cosenos. Temporizamos el bucle y, para evitar una optimización no deseada del compilador, pretendemos usar el resultado.
Compilando y ejecutando los ejemplos
En una máquina Linux de 8 núcleos con GCC versión 4.4, los códigos C se pueden compilar y ejecutar de la siguiente manera:
$ gcc -std=c99 -O3 -fopenmp loop.c -o loopc -lm
$ OMP_NUM_THREADS=1 ./loopc
Computing 1000000 cosines and summing them with 1 threads took 0.095832s
$ OMP_NUM_THREADS=2 ./loopc
Computing 1000000 cosines and summing them with 2 threads took 0.047637s
$ OMP_NUM_THREADS=4 ./loopc
Computing 1000000 cosines and summing them with 4 threads took 0.024498s
$ OMP_NUM_THREADS=8 ./loopc
Computing 1000000 cosines and summing them with 8 threads took 0.011785s
Para la versión de Fortran, da:
$ gfortran -O3 -fopenmp loop.f90 -o loopf
$ OMP_NUM_THREADS=1 ./loopf
Computing 1000000 cosines and summing them with 1 threads took 0.0915s
$ OMP_NUM_THREADS=2 ./loopf
Computing 1000000 cosines and summing them with 2 threads took 0.0472s
$ OMP_NUM_THREADS=4 ./loopf
Computing 1000000 cosines and summing them with 4 threads took 0.0236s
$ OMP_NUM_THREADS=8 ./loopf
Computing 1000000 cosines and summing them with 8 threads took 0.0118s
Adición de dos vectores usando OpenMP paralelo para construir
void parallelAddition (unsigned N, const double *A, const double *B, double *C)
{
unsigned i;
#pragma omp parallel for shared (A,B,C,N) private(i) schedule(static)
for (i = 0; i < N; ++i)
{
C[i] = A[i] + B[i];
}
}
Este ejemplo agrega dos vectores (A
y B
en C
) generando un equipo de subprocesos (especificados por la variable de entorno OMP_NUM_THREADS
, por ejemplo) y asignando a cada subproceso una parte del trabajo (en este ejemplo, asignado estáticamente a través de la expresión schedule(static)
).
Ver la sección de comentarios con respecto a la opcionalidad private(i)
.