openmp
Loop parallelismo in OpenMP
Ricerca…
Parametri
Clausola | Parametro |
---|---|
private | Elenco separato da virgola di variabili private |
firstprivate | Come private , ma inizializzato sul valore della variabile prima di entrare nel ciclo |
lastprivate | Come private , ma la variabile otterrà il valore corrispondente all'ultima iterazione del ciclo all'uscita |
reduction | operatore di riduzione : elenco separato da virgole delle corrispondenti variabili di riduzione |
schedule | static , dynamic , guided , auto o runtime con una dimensione facoltativa del blocco dopo una coma per il 3 precedente |
collapse | Numero di anelli perfettamente annidati per collassare e parallelizzare insieme |
ordered | Indica che alcune parti del ciclo dovranno essere mantenute in ordine (queste parti saranno identificate in modo specifico con alcune clausole ordered all'interno del corpo del loop) |
nowait | Rimuovere la barriera implicita esistente per impostazione predefinita alla fine del costrutto del ciclo |
Osservazioni
Il significato della clausola del schedule
è il seguente:
-
static[,chunk]
: Distribuisci staticamente (il che significa che la distribuzione viene eseguita prima di entrare nel ciclo) le iterazioni del ciclo in batch di dimensioni delchunk
in modalità round robin. Se ilchunk
non è specificato, i blocchi sono il più equi possibile e ogni thread ottiene al massimo uno di essi. -
dynamic[,chunk]
: Distribuisce le iterazioni del ciclo tra i thread per lotti di dimensioni delchunk
con una politica first-come-first-served, finché non rimane alcun batch. Se non specificato, ilchunk
è impostato su 1 -
guided[,chunk]
: comedynamic
ma con lotti di dimensioni sempre più piccole, fino a 1 -
auto
: lascia che il compilatore e / o la libreria di runtime eseguano la scelta migliore -
runtime
: Deffer la decisione in fase di esecuzione per mezzo della variabile di ambienteOMP_SCHEDULE
. Se in fase di esecuzione la variabile di ambiente non è definita, verrà utilizzata la pianificazione predefinita
L'impostazione predefinita per la schedule
è definita dall'implementazione . In molti ambienti è static
, ma può anche essere dynamic
o potrebbe essere molto auto
. Pertanto, fai attenzione che la tua implementazione non si basi implicitamente su di essa senza impostarla esplicitamente.
Negli esempi sopra, abbiamo usato la forma fusa parallel for
o parallel do
. Tuttavia, il costrutto del loop può essere utilizzato senza fonderlo con la direttiva parallel
, sotto forma di un #pragma omp for [...]
o !$omp do [...]
direttiva !$omp do [...]
autonoma all'interno di una regione parallel
.
Solo per la versione Fortran, le variabili dell'indice di loop dei loop in parallelo sono (sono) sempre private
per impostazione predefinita. Non è quindi necessario dichiararli esplicitamente in private
(sebbene farlo non sia un errore).
Per la versione C e C ++, gli indici di loop sono come qualsiasi altra variabile. Pertanto, se il loro ambito si estende al di fuori del / i ciclo / i in parallelo (ovvero se non sono dichiarati come for ( int i = ...)
ma piuttosto come int i; ... for ( i = ... )
allora essi devono essere dichiarati private
.
Esempio tipico in 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;
}
In questo esempio, calcoliamo solo 1 milione di coseni e sommiamo i loro valori in parallelo. Eseguiamo anche l'esecuzione per vedere se la parallelizzazione ha alcun effetto sulla performance. Infine, dato che misuriamo il tempo, dobbiamo assicurarci che il compilatore non ottimizzi il lavoro che abbiamo svolto, quindi fingiamo di usare il risultato semplicemente restituendolo.
Stesso esempio in 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
Qui di nuovo calcoliamo e accumuliamo 1 milione di coseni. Facciamo il time loop e per evitare l'ottimizzazione indesiderata del compilatore, a parte questo, fingiamo di usare il risultato.
Compilare ed eseguire gli esempi
Su una macchina Linux a 8 core che utilizza GCC versione 4.4, i codici C possono essere compilati ed eseguiti nel seguente modo:
$ 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
Per la versione Fortran, dà:
$ 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
Aggiunta di due vettori usando OpenMP parallel per costruire
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];
}
}
Questo esempio aggiunge due vettori ( A
e B
in C
) creando un gruppo di thread (specificato dalla variabile di ambiente OMP_NUM_THREADS
, ad esempio) e assegnando a ciascun thread un blocco di lavoro (in questo esempio, assegnato staticamente alla schedule(static)
espressione).
Vedere la sezione commenti riguardo l'opzionalità private(i)
.