Le nombre de threads peut être contrôlé à partir du programme ou en utilisant la variable d'environnement OMP_NUM_THREADS
par exemple :
export OMP_NUM_THREADS=4
Compilation :
C/C++ | Fortran |
---|---|
$ icc -openmp omp_pgm.c -o pgm $ gcc -fopenmp omp_pgm.c -o pgm | $ ifort -openmp omp_pgm.f -o pgm $ gfortran -fopenmp omp_pgm.f -o pgm |
Un bloc de code exécuté par plusieurs threads en parallèle.
Fortran :
!$OMP PARALLEL [ clause [ [ , ] clause ] ... ] structured-block !$OMP END PARALLEL
C/C++ :
#pragma omp parallel [ clause [ clause ]...] structured-block
Les clauses possibles :
if (scalar expression) private (list) shared (list) default (none|shared|private) reduction (operator: list) firstprivate (list) num_threads (scalar_int_expr)
REDUCTION
est utilisée pour les opérations de réduction avec synchronisation implicite entre les tâches (CF. boucle parallèle)NUM_THREAD
permet de spécifier le nombre de threads à l'entrée d'une région parallèle. Il s'agit de l'équivalent de l'appel de fonction OMP_SET_NUM_THREADSCALL OMP_SET_DYNAMIC OMP_DYNAMIC=true
program hello !$OMP PARALLEL print *,'hello world' !$OMP END PARALLEL stop end program hello
#include <stdio.h> #include <omp.h> int main(int argc, char** argv) { int i=-1; #pragma omp parallel private (i) { i=omp_get_thread_num(); printf( "hello world from %d",i); } return 0; }
export OMP_NUM_THREADS=4 $./hello hello world from 1 hello world from 0 hello world from 3 hello world from 2
nowait
pour continuer l'exécution sans attendreDO/FOR
dans une même région parallèle C/C++ | Fortran |
---|---|
#pragma omp for[clause [clause]…] for loop | !$omp do[clause [clause]…] do loop [!$omp end do [nowait]] |
Les clauses possibles :
private(list) firstprivate(list) lastprivate(list) reduction(operator: list) ordered schedule(kind [, chunk_size])
La clause schedule(type,[chunk])
permet de spécifier le mode de répartition des itérations sur l'ensemble de threads :
« chunk »
est la taille de chaque bloc à traiter (nombre d'itérations), « type »
mode de distribution :
OMP_SCHEDULE
Exemples
C/C++ | C/C++ |
---|---|
#pragma omp parallel shared(a,b) private(j) { #pragma omp for for (j=0; j<N; j++) a[j] = a[j] + b[j]; } | #pragma omp parallel for shared(a,b) private(j) { for (j=0; j<N; j++) a[j] = a[j] + b[j]; } |
Fortran | C/C++ |
!$omp do shared(x) private(i) schedule(runtime) do i = 1, 12000 x(i)=a end do | #pragma omp parallel for private(i), shared(x) schedule (guided,10) for (i=0;i<1000;i++) x(i)=f(i); |
La réduction est une opération associative appliquée à une variable partagée.
reduction(operator|intrinsic:var1[,var2])
Les variable doivent être partagées
opérateurs : +, *, -, .and., .or., .eqv., .neqv. intrinsic : max, min,iand,ior, ieor
opérateurs : +, *, -, &, ^, |, &&, ||
Exemple
program parallel implicit none integer, parameter :: n=5 integer :: i, s=0, p=1, r=1 !$OMP PARALLEL !$OMP DO REDUCTION(+:s) REDUCTION(*:p,r) do i = 1, n s = s + 1 p = p * 2 r = r * 3 end do !$OMP END DO !$OMP END PARALLEL print *,"s =",s, "; p =",p, "; r =",r end program parallel
S=5 ; p= 32; r = 243
Fortran | C/C++ |
---|---|
!$omp sections[clause[,clause]...] !$omp section code block [!$omp section another code block [!$omp section …]] !$omp end sections[nowait] | #pragma omp sections[clause [clause...]] { #pragma omp section structured block [#pragma omp section structured block …] } |
Les clauses possibles :
private(list) firstprivate(list) lastprivate(list) reduction(operator|intrinsic:list) nowait
Une manière plus simple de créer une section parallèle est de combiner les directives de région parallèle et de section parallèle Dans ce cas :
Exemples
Fortran | C/C++ |
---|---|
!$OMP PARALLEL SECTIONS !$OMP SECTION CALL INIT(A) $OMP SECTION CALL INIT(B) !$OMP END PARALLEL SECTIONS | #pragma omp parallel sections { #pragma omp section init(A); #pragma omp section init(B); } |
Pour bien paralléliser une boucle, le travail effectué dans une itération de la boucle ne doit pas dépendre du travail effectué dans une autre itération. En d'autres termes, l'ordre d'exécution des itérations de la boucle doit être pertinent. Certaines dépendances de données peuvent être évitées en modifiant le code.
Observons le code suivant :
!$OMP PARALLEL DO PRIVATE(id) do i = 1, n a[i] = f(a[i-1]); end do !$OMP END PARALLEL
a
.
Existe-il une dépendance de donnée ?
do i = 2,n,2 a(i) = c*a(i-1) end do | do i = 1,n a(i) = c * a(idx(i)) enddo |
Calcul de PI
par intégration numérique (méthode des trapèzes).
Voici la version séquentielle :
PROGRAM pi IMPLICIT NONE INTEGER, PARAMETER :: nb_iter=100000000 DOUBLE PRECISION :: pas, x, pi_approchee = 0.0D0, temps INTEGER :: i, t1, t2, ir pas = 1.0D0 / nb_iter CALL SYSTEM_CLOCK(count=t1) DO i=1, nb_iter x = pas * (i - 0.5D0) pi_approchee = pi_approchee + 4.0D0 * pas / (1.0D0 + x * x) END DO CALL SYSTEM_CLOCK(count=t2, count_rate=ir) temps = REAL(t2 - t1) / ir PRINT '(" Valeur approchee de PI = ", F16.14)', pi_approchee PRINT '(" Temps d''execution : ", F7.3, " sec.")', temps END PROGRAM pi
Pour la version parallèle, il suffit de paralléliser la boucle avec une réduction :
!$OMP PARALLEL DO PRIVATE (i,x) REDUCTION(+:pi_approchee)
Exécution :
On utilise 8
threads pour l'exécution.
export OMP_NUM_THREADS=8
Temps d'exécution en séquentiel :
Temps d'exécution en parallèle :
L'accélération :
p
est le nombre de threads ou processeurs.