Parallélisme de boucle dans OpenMP
Paramètres
Clause | Paramètre |
---|---|
privé |
Liste de variables privées séparées par des virgules |
premierprivé |
Comme private , mais initialisé à la valeur de la variable avant d’entrer dans la boucle |
dernierprivé |
Comme private , mais la variable obtiendra la valeur correspondant à la dernière itération de la boucle lors de la sortie |
réduction |
opérateur de réduction : liste séparée par des virgules des variables de réduction correspondantes |
horaire |
static , dynamic , guided , auto ou runtime avec une taille de bloc optionnelle après une virgule pour les 3 premiers |
réduire |
Nombre de boucles parfaitement imbriquées à réduire et à paralléliser ensemble |
commandé |
Indique que certaines parties de la boucle devront être conservées dans l’ordre (ces parties seront spécifiquement identifiées avec des clauses “ordonnées” à l’intérieur du corps de la boucle) |
maintenant |
Supprimer la barrière implicite existant par défaut à la fin de la construction de la boucle |
La signification de la clause “schedule” est la suivante :
static[,chunk]
: distribue statiquement (ce qui signifie que la distribution est effectuée avant d’entrer dans la boucle) les itérations de la boucle par lots de la taille dechunk
de manière circulaire. Sichunk
n’est pas spécifié, alors les morceaux sont aussi égaux que possible et chaque thread en reçoit au plus un.dynamic[,chunk]
: Distribuez les itérations de boucle parmi les threads par lots de taillechunk
avec une politique du premier arrivé, premier servi, jusqu’à ce qu’il ne reste plus de lot. S’il n’est pas spécifié,chunk
est défini sur 1guided[,chunk]
: commedynamic
mais avec des lots dont les tailles deviennent de plus en plus petites, jusqu’à 1auto
: laissez le compilateur et/ou la bibliothèque d’exécution décider ce qui convient le mieuxruntime
: Diffère la décision à l’exécution au moyen de la variable d’environnementOMP_SCHEDULE
. Si au moment de l’exécution la variable d’environnement n’est pas définie, la planification par défaut sera utilisée
La valeur par défaut pour schedule
est définition d’implémentation. Dans de nombreux environnements, il est “statique”, mais peut également être “dynamique” ou très bien “auto”. Par conséquent, veillez à ce que votre implémentation ne s’appuie pas implicitement sur elle sans la définir explicitement.
Dans les exemples ci-dessus, nous avons utilisé la forme fusionnée “parallel for” ou “parallel do”. Cependant, la construction de boucle peut être utilisée sans la fusionner avec la directive parallel
, sous la forme d’une directive autonome #pragma omp for [...]
ou !$omp do [...]
dans un région “parallèle”.
Pour la version Fortran uniquement, la ou les variables d’index de boucle de la ou des boucles parallélisées est (sont) toujours “privée” par défaut. Il n’est donc pas nécessaire de les déclarer explicitement “privés” (bien que cela ne soit pas une erreur).
Pour la version C et C++, les index de boucle sont comme n’importe quelle autre variable. Par conséquent, si leur portée s’étend en dehors de la ou des boucles parallélisées (c’est-à-dire si elles ne sont pas déclarées comme for ( int i = ...)
mais plutôt comme `int i; … for ( i = … )’ alors ils doivent être déclarés ‘privés’.
Exemple typique 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;
}
Dans cet exemple, nous calculons simplement 1 million de cosinus et additionnons leurs valeurs en parallèle. Nous chronométrons également l’exécution pour voir si la parallélisation a un effet sur les performances. Enfin, puisque nous mesurons le temps, nous devons nous assurer que le compilateur n’optimisera pas le travail que nous avons fait, nous faisons donc semblant d’utiliser le résultat en le renvoyant simplement.
Même exemple 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
Ici encore, nous calculons et accumulons 1 million de cosinus. Nous chronométrons la boucle et pour éviter une optimisation indésirable du compilateur, nous faisons semblant d’utiliser le résultat.
Compilation et exécution des exemples
Sur une machine Linux à 8 cœurs utilisant GCC version 4.4, les codes C peuvent être compilés et exécutés de la manière suivante :
$ 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
Pour la version Fortran, cela donne :
$ 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
Ajout de deux vecteurs utilisant OpenMP parallèle pour la construction
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];
}
}
Cet exemple ajoute deux vecteurs (A
et B
dans C
) en créant une équipe de threads (spécifiés par la variable d’environnement OMP_NUM_THREADS
, par exemple) et en attribuant à chaque thread un morceau de travail (dans cet exemple, attribué statiquement via l’expression schedule(static)
).
Voir la section des remarques concernant l’option “private(i)”.