2. Directives
Les directives sont basées sur des directives #pragma
définies dans les normes C et C++. Les compilateurs qui prennent en charge l’API C et C++ OpenMP incluent une option de ligne de commande qui active et autorise l’interprétation de toutes les directives du compilateur OpenMP.
2.1 Format des directives
La syntaxe d’une directive OpenMP est formellement spécifiée par la grammaire dans l’annexe C, et de manière informelle de la manière suivante :
#pragma omp directive-name [clause[ [,] clause]...] new-line
Chaque directive commence par #pragma omp
, afin de réduire le risque de conflit avec d’autres directives pragma (non OpenMP ou d’extensions de fournisseur à OpenMP) portant le même nom. Le reste de la directive suit les conventions des normes C et C++ pour les directives du compilateur. En particulier, l’espace blanc peut être utilisé avant et après #
, et parfois l’espace blanc doit être utilisé pour séparer les mots d’une directive. Les jetons de prétraitement qui suivent #pragma omp
sont soumis au remplacement des macros.
Les directives sont sensibles à la casse. L’ordre dans lequel les clauses apparaissent dans les directives n’est pas important. Les clauses sur les directives peuvent être répétées si nécessaire, sous réserve des restrictions répertoriées dans la description de chaque clause. Si la liste de variables variable-list apparaît dans une clause, elle doit spécifier uniquement des variables. Un seul nom de directive directive-name peut être spécifié par directive. Par exemple, la directive suivante n’est pas autorisée :
/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier
Une directive OpenMP s’applique à maximum une instruction réussie, qui doit être un bloc structuré.
2.2 Compilation conditionnelle
Le nom de macro _OPENMP
est défini par les implémentations compatibles avec OpenMP comme constante décimale aaaamm, qui sera l’année et le mois de la spécification approuvée. Cette macro ne doit pas être l’objet d’un #define
ou d’une directive de prétraitement #undef
.
#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif
Si les fournisseurs définissent des extensions pour OpenMP, ils peuvent spécifier des macros prédéfinies supplémentaires.
2.3 Construction parallel
La directive suivante définit une région parallèle, qui représente une région du programme à exécuter par de nombreux threads en parallèle. Cette directive est la construction fondamentale qui démarre l’exécution parallèle.
#pragma omp parallel [clause[ [, ]clause] ...] new-line structured-block
La clause est l’une des clauses suivantes :
if(
scalar-expression)
private(
variable-list)
firstprivate(
variable-list)
default(shared | none)
shared(
variable-list)
copyin(
variable-list)
reduction(
operator:
variable-list)
num_threads(
integer-expression)
Lorsqu’un thread accède à une construction parallel, une équipe de threads est créée dans les cas suivants :
- Aucune clause
if
n’est présente. - L’expression
if
prend une valeur différente de zéro.
Ce thread devient le thread principal de l’équipe, avec un nombre de threads de 0 et tous les threads de l’équipe, y compris le thread principal, exécutent la région en parallèle. Si la valeur de l’expression if
est égale à zéro, la région est sérialisée.
Pour déterminer le nombre de threads demandés, les règles suivantes sont prises en compte dans l’ordre. La première règle dont la condition est remplie sera appliquée :
Si la clause
num_threads
est présente, la valeur de l’expression entière est le nombre de threads demandés.Si la fonction de bibliothèque
omp_set_num_threads
a été appelée, la valeur de l’argument dans l’appel exécuté le plus récemment est le nombre de threads demandés.Si la variable d’environnement
OMP_NUM_THREADS
est définie, la valeur de cette variable d’environnement est le nombre de threads demandés.Si aucune des méthodes ci-dessus n’est utilisée, le nombre de threads demandés est défini par l’implémentation.
Si la clause num_threads
est présente, elle remplace le nombre de threads demandés par la fonction de bibliothèque omp_set_num_threads
ou la variable d’environnement OMP_NUM_THREADS
uniquement pour la région parallèle à laquelle elle est appliquée. Les régions parallèles ultérieures ne sont pas affectées par celle-ci.
Le nombre de threads qui exécutent la région parallèle dépend également de l’ajustement dynamique du nombre de threads activé. Si l’ajustement dynamique est désactivé, le nombre demandé de threads exécute la région parallèle. Si l’ajustement dynamique est activé, le nombre demandé de threads est le nombre maximal de threads qui peuvent exécuter la région parallèle.
Si une région parallèle est rencontrée alors que l’ajustement dynamique du nombre de threads est désactivé et que le nombre de threads demandés pour la région parallèle est supérieur au nombre que le système d’exécution peut fournir, le comportement du programme est défini par l’implémentation. Une implémentation peut, par exemple, interrompre l’exécution du programme ou sérialiser la région parallèle.
La fonction de bibliothèque omp_set_dynamic
et la variable d’environnement OMP_DYNAMIC
peuvent être utilisées pour activer et désactiver l’ajustement dynamique du nombre de threads.
Le nombre de processeurs physiques hébergeant réellement les threads à un moment donné est défini par l’implémentation. Une fois créé, le nombre de threads de l’équipe reste constant pendant la durée de cette région parallèle. Il peut être modifié explicitement par l’utilisateur ou automatiquement par le système d’exécution d’une région parallèle à une autre.
Les instructions contenues dans l’étendue dynamique de la région parallèle sont exécutées par chaque thread, et chaque thread peut exécuter un chemin d’instructions différent des autres threads. Les directives rencontrées en dehors de l’étendue lexicale d’une région parallèle sont appelées des directives orphelines.
Une barrière implicite est présente à la fin d’une région parallèle. Seul le thread principal de l’équipe continue l’exécution à la fin d’une région parallèle.
Si un thread d’une équipe exécutant une région parallèle rencontre une autre construction parallel, il crée une équipe et devient le principal de cette nouvelle équipe. Les régions parallèles imbriquées sont sérialisées par défaut. Par conséquent, par défaut, une région parallèle imbriquée est exécutée par une équipe composée d’un thread. Le comportement par défaut peut être modifié à l’aide de la fonction de bibliothèque runtime omp_set_nested
ou de la variable d’environnement OMP_NESTED
. Toutefois, le nombre de threads d’une équipe qui exécutent une région parallèle imbriquée est défini par l’implémentation.
Les restrictions à la directive parallel
sont les suivantes :
Tout au plus, une clause
if
peut apparaître sur la directive.Il n’est pas spécifié si des effets secondaires dans l’expression If ou
num_threads
se produisent.Un
throw
exécuté à l’intérieur d’une région parallèle doit entraîner la reprise de l’exécution dans l’étendue dynamique du même bloc structuré, et il doit être intercepté par le même thread qui a levé l’exception.Seulement une clause
num_threads
peut apparaître sur la directive. L’expressionnum_threads
est évaluée en dehors du contexte de la région parallèle et doit être évaluée à une valeur entière positive.L’ordre d’évaluation des clauses
if
etnum_threads
n’est pas spécifié.
Références croisées
- Clauses
private
,firstprivate
,default
,shared
,copyin
etreduction
(section 2.7.2) - Variable d’environnement OMP_NUM_THREADS
- Fonction de bibliothèque omp_set_dynamic
- Variable d’environnement OMP_DYNAMIC
- Fonction omp_set_nested
- Variable d’environnement OMP_NESTED
- Fonction de bibliothèque omp_set_num_threads
2.4 Constructions de partage de travail
Une construction de partage de travail distribue l’exécution de l’instruction associée parmi les membres de l’équipe qui la rencontrent. Les directives de partage de travail ne lancent pas de nouveaux threads et n’impliquent aucun obstacle à l’entrée d’une construction de partage de travail.
La séquence de constructions de partage de travail et des directives barrier
rencontrées doit être la même pour chaque thread d’une équipe.
OpenMP définit les constructions de partage de travail suivantes, et ces constructions sont décrites dans les sections suivantes :
2.4.1 Construction For
La directive for
identifie une construction de partage de travail itérative qui spécifie que les itérations de la boucle associée seront exécutées en parallèle. Les itérations de la boucle for
sont réparties entre les threads qui existent déjà dans l’équipe exécutant la construction parallel à laquelle elle se lie. La syntaxe de la construction for
est la suivante :
#pragma omp for [clause[[,] clause] ... ] new-line for-loop
La clause est l’une des clauses suivantes :
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
operator:
variable-list)
ordered
schedule(
kind [,
chunk_size])
nowait
La directive for
place des restrictions sur la structure de la boucle for
correspondante. Plus précisément, la boucle for
correspondante doit avoir une forme canonique :
for (
init-expr ;
var logical-op b ;
incr-expr )
init-expr
Celui-ci peut avoir l'une des valeurs suivantes :
- var = lb
- integer-type var = lb
incr-expr
Celui-ci peut avoir l'une des valeurs suivantes :
++
var- var
++
--
var- var
--
- var
+=
incr - var
-=
incr - var
=
var+
incr - var
=
incr+
var - var
=
var-
incr
var
Variable entière signée. Au lieu d’être partagée, cette variable est implicitement rendue privée pendant la durée du for
. Ne modifiez pas cette variable dans le corps de l’instruction for
. Sauf si lastprivate
est spécifié pour la variable, sa valeur après la boucle est indéterminée.
logical-op
Celui-ci peut avoir l'une des valeurs suivantes :
<
<=
>
>=
lb, bet incr
Expressions entières invariantes de boucle. Il n’y a aucune synchronisation pendant l’évaluation de ces expressions, donc tous les effets secondaires évalués produisent des résultats indéterminés.
Le formulaire canonique permet de calculer le nombre d’itérations de boucles lors de l’entrée dans la boucle. Ce calcul est effectué avec des valeurs dans le type de var, après les promotions intégrales. En particulier, si la valeur de b -
lb +
incr ne peut être représentée dans ce type, le résultat est indéterminé. En outre, si logical-op est <
ou <=
, incr-expr doit entraîner l’augmentation de var sur chaque itération de la boucle. Si logical-op est >
ou >=
, incr-expr doit entraîner la diminution de var sur chaque itération de la boucle.
La clause schedule
spécifie comment les itérations de la boucle for
sont divisées entre les threads de l’équipe. La justesse d’un programme ne doit pas dépendre du thread qui exécute une itération particulière. La valeur de chunk_size, si elle est spécifiée, doit être une expression entière invariante de boucle avec une valeur positive. Il n’y a aucune synchronisation pendant l’évaluation de cette expression, donc tout effet secondaire évalué produit des résultats indéterminés. La planification kind peut être l’une des valeurs suivantes :
Tableau 2-1 : valeurs kind de la clause schedule
Valeur | Description |
---|---|
static | Lorsque schedule(static, chunk_size ) est spécifié, les itérations sont divisées en segments d’une taille spécifiée par chunk_size. Les segments sont attribués statiquement aux threads au sein de l’équipe à la manière d’un tourniquet dans l’ordre du numéro du thread. Lorsqu’aucun chunk_size n’est spécifié, l’espace d’itération est divisé en segments de taille approximativement égale, avec un segment affecté à chaque thread. |
dynamic | Lorsque schedule(dynamic, chunk_size ) est spécifié, les itérations sont divisées en une série de segments, chacune contenant des itérations chunk_size. Chaque segment est affecté à un thread qui attend une affectation. Le thread exécute le segment d’itérations, puis attend son affectation suivante, jusqu’à ce qu’aucun segment ne reste affecté. Le dernier segment à affecter peut avoir un plus petit nombre d’itérations. Lorsqu’aucun chunk_size n’est spécifié, la valeur par défaut est 1. |
guidé | Lorsque schedule(guided, chunk_size ) est spécifié, les itérations sont affectées aux threads en segments avec des tailles décroissantes. Lorsqu’un thread termine son segment d’itérations attribué, un autre segment lui est affecté dynamiquement, jusqu’à ce qu’il n’en reste plus. Pour un chunk_size de 1, la taille de chaque segment est approximativement le nombre d’itérations non attribuées divisé par le nombre de threads. Ces tailles diminuent de manière presque exponentielle jusqu’à 1. Pour un chunk_size dont la valeur k est supérieure à 1, les tailles diminuent de manière presque exponentielle jusqu’à k, sauf que le dernier bloc peut avoir moins de k itérations. Lorsqu’aucun chunk_size n’est spécifié, la valeur par défaut est 1. |
runtime | Lorsque schedule(runtime) est spécifié, la décision concernant la planification est différée jusqu’à l’exécution. La planification kind et la taille des segments peuvent être choisies au moment de l’exécution en définissant la variable d’environnement OMP_SCHEDULE . Si cette variable d’environnement n’est pas définie, la planification résultante est définie par l’implémentation. Lorsque schedule(runtime) est spécifié, chunk_size ne doit pas être spécifié. |
En l’absence d’une clause schedule
définie explicitement, le schedule
par défaut est défini par l’implémentation.
Un programme conforme à OpenMP ne doit pas s’appuyer sur une planification particulière pour une exécution correcte. Un programme ne doit pas s’appuyer sur une planification kind précisément conforme à la description indiquée ci-dessus, car des variations sont possibles dans les implémentations de la même planification kind entre différents compilateurs. Les descriptions peuvent être utilisées pour sélectionner la planification appropriée pour une situation particulière.
La clause ordered
doit être présente lorsque les directives ordered
sont liées à la construction for
.
Une barrière implicite est présente à la fin d’une construction for
, sauf si une clause nowait
est spécifiée.
Voici les restrictions à la directive for
:
La boucle
for
doit être un bloc structuré et, en outre, son exécution ne doit pas être arrêtée par une instructionbreak
.Les valeurs des expressions de contrôle de boucle de la boucle
for
associée à une directivefor
doivent être identiques pour tous les threads de l’équipe.La variable d’itération de boucle
for
doit avoir un type entier signé.Seulement une clause
schedule
peut apparaître sur une directivefor
.Seulement une clause
ordered
peut apparaître sur une directivefor
.Seulement une clause
nowait
peut apparaître sur une directivefor
.Il n’est pas spécifié si des effets secondaires dans les expressions chunk_size, lb, b ou incr se produisent, ou combien de fois ils se produisent.
La valeur de l’expression chunk_size doit être la même pour tous les threads de l’équipe.
Références croisées
- Clauses
private
,firstprivate
,lastprivate
etreduction
(section 2.7.2) - Variable d’environnement OMP_SCHEDULE
- Construction ordered
- Clause schedule
2.4.2 Construction sections
La directive sections
identifie une construction de partage de travail non itérative qui spécifie un ensemble de constructions à diviser entre les threads d’une équipe. Chaque section est exécutée une fois par un thread dans l’équipe. La syntaxe de la directive sections
est la suivante :
#pragma omp sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
La clause est l’une des clauses suivantes :
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
operator:
variable-list)
nowait
Chaque section est précédée d’une directive section
, bien que la directive section
soit facultative pour la première section. Les directives section
doivent apparaître dans l’étendue lexicale de la directive sections
. Une barrière implicite est présente à la fin d’une construction sections
, sauf si un nowait
est spécifié.
Voici les restrictions à la directive sections
:
Une directive
section
ne doit pas apparaître en dehors de l’étendue lexicale de la directivesections
.Seulement une clause
nowait
peut apparaître sur une directivesections
.
Références croisées
- Clauses
private
,firstprivate
,lastprivate
etreduction
(section 2.7.2)
2.4.3 Construction single
La directive single
identifie une construction qui spécifie que le bloc structuré associé est exécuté par un seul thread de l’équipe (pas nécessairement le thread principal). La syntaxe de la directive single
est la suivante :
#pragma omp single [clause[[,] clause] ...] new-linestructured-block
La clause est l’une des clauses suivantes :
private(
variable-list)
firstprivate(
variable-list)
copyprivate(
variable-list)
nowait
Une barrière implicite est présente après la construction single
, sauf si une clause nowait
est spécifiée.
Voici les restrictions à la directive single
:
- Seulement une clause
nowait
peut apparaître sur une directivesingle
. - La clause
copyprivate
ne doit pas être utilisée avec la clausenowait
.
Références croisées
- Clauses
private
,firstprivate
etcopyprivate
(section 2.7.2)
2.5 Constructions de partage de travail et parallel combinées
Les constructions de partage de travail et parallel combinées sont des raccourcis pour spécifier une région parallèle qui n’a qu’une seule construction de partage de travail. La sémantique de ces directives est la même que la spécification explicite d’une directive parallel
suivie d’une construction de partage de travail unique.
Les sections suivantes décrivent les constructions de partage de travail et parallel combinées :
- Directive parallel for
- Directive parallel sections
2.5.1 Construction parallel for
La directive parallel for
est un raccourci pour une région parallel
qui ne contient qu’une seule directive for
. La syntaxe de la directive parallel for
est la suivante :
#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop
Cette directive autorise toutes les clauses de la directive parallel
et de la directive for
, à l’exception de la clause nowait
, avec des significations et des restrictions identiques. La sémantique est la même que la spécification explicite d’une directive parallel
immédiatement suivie d’une directive for
.
Références croisées
- Directive parallel
- Directive for
- Clauses d’attribut de données
2.5.2 Construction parallel sections
La directive parallel sections
fournit un formulaire de raccourci permettant de spécifier une région parallel
qui n’a qu’une seule directive sections
. La sémantique est la même que la spécification explicite d’une directive parallel
immédiatement suivie d’une directive sections
. La syntaxe de la directive parallel sections
est la suivante :
#pragma omp parallel sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
La clause peut être l’une des clauses acceptées par les directives parallel
et sections
, à l’exception de la clause nowait
.
Références croisées
2.6 Directives principales et de synchronisation
Ces opérations sont décrites dans les sections suivantes :
- Construction master
- Construction critical
- Directive barrier
- Construction atomic
- Directive flush
- Construction ordered
2.6.1 Construction master
La directive master
identifie une construction qui spécifie un bloc structuré exécuté par le thread principal de l’équipe. La syntaxe de la directive master
est la suivante :
#pragma omp master new-linestructured-block
Les autres threads de l’équipe n’exécutent pas le bloc structuré associé. Il n’existe aucune barrière implicite à l’entrée ou à la sortie de la construction master.
2.6.2 Construction critical
La directive critical
identifie une construction qui limite l’exécution du bloc structuré associé à un seul thread à la fois. La syntaxe de la directive critical
est la suivante :
#pragma omp critical [(name)] new-linestructured-block
Un nom facultatif peut être utilisé pour identifier la région critique. Les identificateurs utilisés pour identifier une région critique ont une liaison externe et se trouvent dans un espace de noms distinct des espaces de noms utilisés par les étiquettes, les balises, les membres et les identificateurs ordinaires.
Un thread attend au début d’une région critique jusqu’à ce qu’aucun autre thread n’exécute une région critique (n’importe où dans le programme) avec le même nom. Toutes les directives critical
sans nom correspondent au même nom non spécifié.
2.6.3 Directive barrier
La directive barrier
synchronise tous les threads d’une équipe. En cas de rencontre, chaque thread de l’équipe attend jusqu’à ce que tous les autres aient atteint ce point. La syntaxe de la directive barrier
est la suivante :
#pragma omp barrier new-line
Une fois que tous les threads de l’équipe ont rencontré la barrière, chaque thread de l’équipe commence à exécuter les instructions après la directive de barrière en parallèle. Étant donné que la directive barrier
n’a pas d’instruction de langage C comprise dans sa syntaxe, certaines restrictions s’appliquent quant à sa disposition au sein d’un programme. Pour plus d’informations sur la grammaire formelle, consultez l’annexe C. L’exemple ci-dessous illustre ces restrictions.
/* ERROR - The barrier directive cannot be the immediate
* substatement of an if statement
*/
if (x!=0)
#pragma omp barrier
...
/* OK - The barrier directive is enclosed in a
* compound statement.
*/
if (x!=0) {
#pragma omp barrier
}
2.6.4 Construction atomic
La directive atomic
garantit qu’un emplacement de mémoire spécifique est mis à jour atomiquement, plutôt que de l’exposer à la possibilité de plusieurs threads d’écriture simultanés. La syntaxe de la directive atomic
est la suivante :
#pragma omp atomic new-lineexpression-stmt
L’instruction d’expression doit avoir l’une des formes suivantes :
- x binop
=
expr - x
++
++
x- x
--
--
x
Dans les expressions précédentes :
x est une expression lvalue avec un type scalaire.
expr est une expression avec un type scalaire et ne fait pas référence à l’objet désigné par x.
binop n’est pas un opérateur surchargé et est l’un des
+
,*
,-
,/
,&
,^
,|
,<<
ou>>
.
Bien que ce soit défini par implémentation si une implémentation remplace toutes les directives atomic
par des directives critical
qui ont le même nom unique, la directive atomic
permet une meilleure optimisation. Souvent, les instructions matérielles sont disponibles pour effectuer la mise à jour atomique avec le moins de surcharge.
Seul le chargement et le magasin de l’objet désigné par x sont atomiques. L’évaluation de expr n’est pas atomique. Pour éviter les conditions de concurrence, toutes les mises à jour de l’emplacement en parallèle doivent être protégées par la directive atomic
, sauf celles qui sont connues pour être libres de conditions de concurrence.
Voici les restrictions à la directive atomic
:
- Toutes les références atomiques à l’emplacement de stockage x dans tout le programme nécessitent un type compatible.
Exemples
extern float a[], *p = a, b;
/* Protect against races among multiple updates. */
#pragma omp atomic
a[index[i]] += b;
/* Protect against races with updates through a. */
#pragma omp atomic
p[i] -= 1.0f;
extern union {int n; float x;} u;
/* ERROR - References through incompatible types. */
#pragma omp atomic
u.n++;
#pragma omp atomic
u.x -= 1.0f;
2.6.5 Directive flush
La directive flush
, qu’elle soit explicite ou implicite, spécifie un point de séquence « inter-thread » auquel l’implémentation est requise pour s’assurer que tous les threads d’une équipe ont une vue cohérente de certains objets (spécifiés ci-dessous) en mémoire. Cela signifie que les évaluations précédentes des expressions qui font référence à ces objets sont complètes et que les évaluations suivantes n’ont pas encore commencé. Par exemple, les compilateurs doivent restaurer les valeurs des objets des registres vers la mémoire, et le matériel peut avoir besoin de vider des mémoires tampons d’écriture vers la mémoire et de recharger les valeurs des objets à partir de la mémoire.
La syntaxe de la directive flush
est la suivante :
#pragma omp flush [(variable-list)] new-line
Si les objets qui nécessitent la synchronisation peuvent tous être désignés par des variables, ces variables peuvent être spécifiées dans la liste variable-list facultative. Si un pointeur est présent dans la liste variable-list, le pointeur lui-même est vidé, et non l’objet auquel le pointeur fait référence.
Une directive flush
sans variable-list synchronise tous les objets partagés, sauf les objets inaccessibles avec une durée de stockage automatique. (Cela peut impliquer une plus grosse surcharge qu’un flush
avec une liste variable-list.) Une directive flush
sans variable-list est implicite pour les directives suivantes :
barrier
- À l’entrée et à la sortie de
critical
- À l’entrée et à la sortie de
ordered
- À l’entrée et à la sortie de
parallel
- À la sortie de
for
- À la sortie de
sections
- À la sortie de
single
- À l’entrée et à la sortie de
parallel for
- À l’entrée et à la sortie de
parallel sections
La directive n’est pas implicite si une clause nowait
est présente. Il convient de noter que la directive flush
n’est pas implicite pour les éléments suivants :
- À l’entrée de
for
- À l’entrée ou à la sortie de
master
- À l’entrée de
sections
- À l’entrée de
single
Une référence qui accède à la valeur d’un objet avec un type qualifié volatile se comporte comme s’il y avait une directive flush
spécifiant cet objet au point de séquence précédent. Une référence qui modifie la valeur d’un objet avec un type qualifié volatile se comporte comme s’il y avait une directive flush
spécifiant cet objet au point de séquence précédent.
Étant donné que la directive flush
n’a pas d’instruction de langage C comprise dans sa syntaxe, certaines restrictions s’appliquent quant à sa disposition au sein d’un programme. Pour plus d’informations sur la grammaire formelle, consultez l’annexe C. L’exemple ci-dessous illustre ces restrictions.
/* ERROR - The flush directive cannot be the immediate
* substatement of an if statement.
*/
if (x!=0)
#pragma omp flush (x)
...
/* OK - The flush directive is enclosed in a
* compound statement
*/
if (x!=0) {
#pragma omp flush (x)
}
Voici les restrictions à la directive flush
:
- Une variable spécifiée dans une directive
flush
ne doit pas avoir de type référence.
2.6.6 Construction ordered
Le bloc structuré suivant une directive ordered
est exécuté dans l’ordre dans lequel les itérations seraient exécutées dans une boucle séquentielle. La syntaxe de la directive ordered
est la suivante :
#pragma omp ordered new-linestructured-block
Une directive ordered
doit se trouver dans l’étendue dynamique d’une construction for
ou parallel for
. La directive for
ou parallel for
à laquelle les liaisons de construction ordered
doivent avoir une clause ordered
spécifiée comme décrit dans la section 2.4.1. Dans l’exécution d’une construction for
ou parallel for
avec une clause ordered
, les constructions ordered
sont exécutées strictement dans l’ordre dans lequel elles seraient exécutées dans une exécution séquentielle de la boucle.
Voici les restrictions à la directive ordered
:
- Une itération d’une boucle avec une construction
for
ne doit pas exécuter la même directive ordered plus d’une fois, et elle ne doit pas exécuter plus d’une directiveordered
.
2.7 Environnement des données
Cette section présente une directive et plusieurs clauses pour contrôler l’environnement de données pendant l’exécution de régions parallèles, de la manière suivante :
Une directive threadprivate est fournie pour créer des variables d’étendue de fichier, d’étendue d’espace de noms ou d’étendue de bloc statiques locales dans un thread.
Les clauses qui peuvent être spécifiées sur les directives pour contrôler les attributs de partage de variables pendant la durée des constructions parallel ou de partage de travail sont décrites dans la section 2.7.2.
2.7.1 Directive threadprivate
La directive threadprivate
rend les variables d’étendue de fichier, d’étendue d’espace de noms ou d’étendue de bloc statique spécifiées dans la liste variable-list privée à un thread. variable-list est une liste séparée par des virgules de variables qui n’ont pas de type incomplet. La syntaxe de la directive threadprivate
est la suivante :
#pragma omp threadprivate(variable-list) new-line
Chaque copie d’une variable threadprivate
est initialisée une fois, à un point non spécifié dans le programme avant la première référence à cette copie, et à la manière habituelle (de la même manière que la copie principale serait initialisée dans une exécution en série du programme). Notez que si un objet est référencé dans un initialiseur explicite d’une variable threadprivate
et que la valeur de l’objet est modifiée avant la première référence à une copie de la variable, le comportement n’est pas spécifié.
Comme pour toute variable privée, un thread ne doit pas référencer la copie d’un autre thread d’un objet threadprivate
. Pendant les régions de séries et les régions principales du programme, les références se feront à la copie du thread principal de l’objet.
Une fois la première région parallèle exécutée, les données des objets threadprivate
sont garanties d’être conservées uniquement si le mécanisme de threads dynamiques a été désactivé et si le nombre de threads reste inchangé pour toutes les régions parallèles.
Voici les restrictions à la directive threadprivate
:
Une directive
threadprivate
pour les variables d’étendue de fichier ou d’étendue d’espace de noms doit apparaître en dehors de toute définition ou déclaration, et doit précéder de manière lexicale toutes les références à l’une des variables de sa liste.Chaque variable de la liste de variables d’une directive
threadprivate
au niveau de l’étendue de fichier ou d’espace de noms doit faire référence à une déclaration de variable au niveau de l’étendue de fichier ou d’espace de noms qui précède la directive de manière lexicale.Une directive
threadprivate
pour les variables d’étendue de bloc statique doit apparaître dans l’étendue de la variable et non dans une étendue imbriquée. La directive doit précéder de manière lexicale toutes les références à des variables de sa liste.Chaque variable de la liste variable-list d’une directive
threadprivate
dans l’étendue de bloc doit faire référence à une déclaration de variable dans la même étendue qui précède la directive de manière lexicale. La déclaration de variable doit utiliser le spécificateur de classe de stockage statique.Si une variable est spécifiée dans une directive
threadprivate
dans une unité de traduction, elle doit être spécifiée dans une directivethreadprivate
dans chaque unité de traduction dans laquelle elle est déclarée.Une variable
threadprivate
ne doit apparaître dans aucune clause, à l’exception de la clausecopyin
,copyprivate
,schedule
,num_threads
ouif
.L’adresse d’une variable
threadprivate
n’est pas une constante d’adresse.Une variable
threadprivate
ne doit pas avoir de type incomplet ou de type référence.Une variable
threadprivate
avec un type de classe non POD doit avoir un constructeur de copie accessible et non ambigu si elle est déclarée avec un initialiseur explicite.
L’exemple suivant montre pourquoi modifier une variable apparaissant dans un initialiseur peut provoquer un comportement non spécifié, et comment éviter ce problème à l’aide d’un objet auxiliaire et d’un constructeur de copie.
int x = 1;
T a(x);
const T b_aux(x); /* Capture value of x = 1 */
T b(b_aux);
#pragma omp threadprivate(a, b)
void f(int n) {
x++;
#pragma omp parallel for
/* In each thread:
* Object a is constructed from x (with value 1 or 2?)
* Object b is copy-constructed from b_aux
*/
for (int i=0; i<n; i++) {
g(a, b); /* Value of a is unspecified. */
}
}
Références croisées
- dynamic threads
- Variable d’environnement OMP_DYNAMIC
2.7.2 Clauses d’attributs de partage de données
Plusieurs directives acceptent des clauses qui permettent à un utilisateur de contrôler les attributs de partage de variables pendant la durée de la région. Les clauses d’attribut de partage s’appliquent uniquement aux variables dans l’étendue lexicale de la directive sur laquelle la clause apparaît. Toutes les clauses suivantes ne sont pas autorisées sur toutes les directives. La liste des clauses valides sur une directive particulière est donnée dans la description de la directive.
Si une variable est visible lorsqu’une construction parallèle ou de partage de travail est rencontrée et que la variable n’est pas spécifiée dans une clause d’attribut de partage ou de directive threadprivate
, la variable est partagée. Les variables statiques déclarées dans l’étendue dynamique d’une région parallèle sont partagées. La mémoire allouée au tas (par exemple, l’utilisation de malloc()
en C ou C++, ou de l’opérateur new
en C++) est partagée. (Le pointeur vers cette mémoire peut toutefois être privé ou partagé.) Les variables dont la durée de stockage automatique est déclarée dans l’étendue dynamique d’une région parallèle sont privées.
La plupart des clauses acceptent un argument variable-list, qui est une liste séparée par des virgules de variables visibles. Si une variable référencée dans une clause d’attribut de partage de données présente un type dérivé d’un modèle et qu’il n’existe aucune autre référence à cette variable dans le programme, le comportement n’est pas défini.
Toutes les variables qui apparaissent dans les clauses de directive doivent être visibles. Les clauses peuvent être répétées si nécessaire, mais aucune variable ne peut être spécifiée dans plus d’une clause, sauf qu’une variable peut être spécifiée à la fois dans une clause firstprivate
et lastprivate
.
Les sections suivantes décrivent les clauses d’attribut de partage de données :
2.7.2.1 private
La clause private
déclare les variables de la liste variable-list qui doivent être privées pour chaque thread d’une équipe. La syntaxe de la clause private
est la suivante :
private(variable-list)
Le comportement d’une variable spécifiée dans une clause private
est le suivant. Un nouvel objet avec une durée de stockage automatique est alloué pour la construction. La taille et l’alignement du nouvel objet sont déterminés par le type de la variable. Cette allocation se produit une fois pour chaque thread de l’équipe, et un constructeur par défaut est appelé pour un objet de classe si nécessaire. Sinon, la valeur initiale est indéterminée. L’objet d’origine référencé par la variable a une valeur indéterminée lors de l’entrée de la construction, il ne doit pas être modifié dans l’étendue dynamique de la construction et il a une valeur indéterminée lors de la sortie de la construction.
Dans l’étendue lexicale de la construction de directive, la variable fait référence au nouvel objet privé alloué par le thread.
Voici les restrictions à la clause private
:
Une variable avec un type de classe spécifié dans une clause
private
doit avoir un constructeur par défaut accessible et non ambigu.Une variable spécifiée dans une clause
private
ne doit pas avoir de type qualifiéconst
, sauf s’il a un type de classe avec un membremutable
.Une variable spécifiée dans une clause
private
ne doit pas avoir de type incomplet ou de type référence.Les variables qui apparaissent dans la clause
reduction
d’une directiveparallel
ne peuvent pas être spécifiées dans une clauseprivate
d’une directive de partage de travail qui est liée à la construction parallel.
2.7.2.2 firstprivate
La clause firstprivate
fournit un surensemble des fonctionnalités fournies par la clause private
. La syntaxe de la clause firstprivate
est la suivante :
firstprivate(variable-list)
Les variables spécifiées dans la liste variable-list ont une sémantique de clause private
, comme décrit dans la section 2.7.2.1. L’initialisation ou la construction se produit comme si elle était effectuée une fois par thread, avant l’exécution du thread de la construction. Pour une clause firstprivate
sur une construction parallel, la valeur initiale du nouvel objet privé est la valeur de l’objet d’origine qui existe immédiatement avant la construction parallel pour le thread qui le rencontre. Pour une clause firstprivate
sur une construction de partage de travail, la valeur initiale du nouvel objet privé pour chaque thread qui exécute la construction de partage de travail est la valeur de l’objet d’origine qui existe avant le moment où le même thread rencontre la construction de partage de travail. En outre, pour les objets C++, le nouvel objet privé pour chaque thread est construit par copie à partir de l’objet d’origine.
Voici les restrictions à la clause firstprivate
:
Une variable spécifiée dans une clause
firstprivate
ne doit pas avoir de type incomplet ou de type référence.Une variable avec un type de classe spécifié
firstprivate
doit avoir un constructeur de copie accessible et non ambigu.Les variables privées dans une région parallèle ou qui apparaissent dans la clause
reduction
d’une directiveparallel
ne peuvent pas être spécifiées dans une clausefirstprivate
d’une directive de partage de travail qui est liée à la construction parallel.
2.7.2.3 lastprivate
La clause lastprivate
fournit un surensemble des fonctionnalités fournies par la clause private
. La syntaxe de la clause lastprivate
est la suivante :
lastprivate(variable-list)
Les variables spécifiées dans la liste variable-list ont une sémantique de clause private
. Lorsqu’une clause lastprivate
apparaît sur la directive qui identifie une construction de partage de travail, la valeur de chaque variable lastprivate
à partir de la dernière itération séquentielle de la boucle associée, ou la dernière directive de section lexicale, est affectée à l’objet d’origine de la variable. Les variables qui ne sont pas affectées à une valeur par la dernière itération de for
ou parallel for
, ou par la dernière section lexicale de la directive sections
ou parallel sections
, ont des valeurs indéterminées après la construction. Les sous-objets non attribués ont également une valeur indéterminée après la construction.
Voici les restrictions à la clause lastprivate
:
Toutes les restrictions pour
private
s’appliquent.Une variable avec un type de classe spécifié
lastprivate
doit avoir un opérateur d’assignation de copie accessible et non ambigu.Les variables privées dans une région parallèle ou qui apparaissent dans la clause
reduction
d’une directiveparallel
ne peuvent pas être spécifiées dans une clauselastprivate
d’une directive de partage de travail qui est liée à la construction parallel.
2.7.2.4 shared
Cette clause partage les variables qui apparaissent dans la liste variable-list parmi tous les threads d’une équipe. Tous les threads d’une équipe accèdent à la même zone de stockage pour les variables shared
.
La syntaxe de la clause shared
est la suivante :
shared(variable-list)
2.7.2.5 default
La clause default
permet à l’utilisateur d’affecter les attributs de partage de données des variables. La syntaxe de la clause default
est la suivante :
default(shared | none)
Spécifier default(shared)
équivaut à répertorier explicitement chaque variable actuellement visible dans une clause shared
, sauf si elle est qualifiée threadprivate
ou const
. En l’absence d’une clause de default
explicite, le comportement par défaut est le même que si default(shared)
était spécifié.
Spécifier default(none)
nécessite qu’au moins l’une des valeurs suivantes soit vraie pour chaque référence à une variable dans l’étendue lexicale de la construction parallèle :
La variable est explicitement répertoriée dans une clause d’attribut de partage de données d’une construction qui contient la référence.
La variable est déclarée dans la construction parallèle.
La variable est
threadprivate
.La variable dispose d’un type qualifié
const
.La variable est la variable de contrôle de boucle pour une boucle
for
qui suit immédiatement une directivefor
ouparallel for
, et la référence de variable apparaît à l’intérieur de la boucle.
Spécifier une variable sur une clause firstprivate
, lastprivate
ou reduction
d’une directive entourée provoque une référence implicite à la variable dans le contexte englobant. Ces références implicites sont également soumises aux exigences répertoriées ci-dessus.
Une seule clause default
peut être spécifiée sur une directive parallel
.
Un attribut de partage de données par défaut d’une variable peut être substitué à l’aide des clauses private
, firstprivate
, lastprivate
, reduction
et shared
, comme illustré dans l’exemple suivant :
#pragma omp parallel for default(shared) firstprivate(i)\
private(x) private(r) lastprivate(i)
2.7.2.6 reduction
Cette clause effectue une réduction sur les variables scalaires qui apparaissent dans la liste variable-list, avec l’opérateur op. La syntaxe de la clause reduction
est la suivante :
reduction(
op :
variable-list )
Une réduction est généralement spécifiée pour une instruction sous l’une des formes suivantes :
- x
=
x op expr - x binop
=
expr - x
=
expr op x (sauf soustraction) - x
++
++
x- x
--
--
x
où :
x
Une des variables de réduction spécifiées dans la liste.
variable-list
Liste séparée par des virgules de variables de réduction scalaire.
expr
Expression avec un type scalaire qui ne fait pas référence à x.
op
Pas un opérateur surchargé, mais un de +
, *
, -
, &
, ^
, |
, &&
ou ||
.
binop
Pas un opérateur surchargé, mais un de +
, *
, -
, &
, ^
, ou |
.
Voici un exemple de la clause reduction
:
#pragma omp parallel for reduction(+: a, y) reduction(||: am)
for (i=0; i<n; i++) {
a += b[i];
y = sum(y, c[i]);
am = am || b[i] == c[i];
}
Comme illustré dans l’exemple, un opérateur peut être masqué à l’intérieur d’un appel de fonction. L’utilisateur doit faire attention à ce que l’opérateur spécifié dans la clause reduction
corresponde à l’opération de réduction.
Bien que les opérandes de droite de l’opérateur ||
n’ait aucun effet secondaire dans cet exemple, elles est autorisées, mais doivent être utilisées avec précaution. Dans ce contexte, un effet secondaire qui est garanti de ne pas se produire pendant l’exécution séquentielle de la boucle peut se produire pendant l’exécution parallèle. Cette différence peut se produire, car l’ordre d’exécution des itérations est indéterminé.
L’opérateur est utilisé pour déterminer la valeur initiale de toutes les variables privées utilisées par le compilateur pour la réduction et pour déterminer l’opérateur de finalisation. Spécifier l’opérateur permet explicitement à l’instruction de réduction d’être en dehors de l’étendue lexicale de la construction. Un nombre quelconque de clauses reduction
peut être spécifié sur la directive, mais une variable peut apparaître dans maximum une clause reduction
pour cette directive.
Une copie privée de chaque variable dans la liste variable-list est créée, une pour chaque thread, comme si la clause private
avait été utilisée. La copie privée est initialisée en fonction de l’opérateur (voir le tableau suivant).
À la fin de la région pour laquelle la clause reduction
a été spécifiée, l’objet d’origine est mis à jour pour refléter le résultat de la combinaison de sa valeur d’origine avec la valeur finale de chacune des copies privées à l’aide de l’opérateur spécifié. Les opérateurs de réduction sont tous associatifs (à l’exception de la soustraction), et le compilateur peut librement réassocier le calcul de la valeur finale. (Les résultats partiels d’une réduction de soustraction sont ajoutés pour former la valeur finale.)
La valeur de l’objet d’origine devient indéterminée lorsque le premier thread atteint la clause conteneur, et le reste jusqu’à ce que le calcul de réduction soit terminé. Normalement, le calcul se termine à la fin de la construction. Toutefois, si la clause reduction
est utilisée sur une construction à laquelle nowait
est également appliquée, la valeur de l’objet d’origine reste indéterminée jusqu’à ce qu’une synchronisation de barrière ait été effectuée pour s’assurer que tous les threads ont terminé la clause reduction
.
Le tableau suivant répertorie les opérateurs valides et leurs valeurs d’initialisation canonique. La valeur d’initialisation réelle est cohérente avec le type de données de la variable de réduction.
Opérateur | Initialisation |
---|---|
+ |
0 |
* |
1 |
- |
0 |
& |
~0 |
| |
0 |
^ |
0 |
&& |
1 |
|| |
0 |
Voici les restrictions à la clause reduction
:
Le type des variables de la clause
reduction
doit être valide pour l’opérateur de réduction, sauf que les types pointeur et référence ne sont jamais autorisés.Une variable spécifiée dans la clause
reduction
ne doit pas être qualifiéeconst
.Les variables privées dans une région parallèle ou qui apparaissent dans la clause
reduction
d’une directiveparallel
ne peuvent pas être spécifiées dans une clausereduction
d’une directive de partage de travail qui est liée à la construction parallel.#pragma omp parallel private(y) { /* ERROR - private variable y cannot be specified in a reduction clause */ #pragma omp for reduction(+: y) for (i=0; i<n; i++) y += b[i]; } /* ERROR - variable x cannot be specified in both a shared and a reduction clause */ #pragma omp parallel for shared(x) reduction(+: x)
2.7.2.7 copyin
La clause copyin
fournit un mécanisme permettant d’affecter la même valeur aux variables threadprivate
pour chaque thread de l’équipe exécutant la région parallèle. Pour chaque variable spécifiée dans une clause copyin
, la valeur de la variable dans le thread principal de l’équipe est copiée, comme si par affectation, aux copies privées de thread au début de la région parallèle. La syntaxe de la clause copyin
est la suivante :
copyin(
variable-list
)
Voici les restrictions à la clause copyin
:
Une variable spécifiée dans la clause
copyin
doit avoir un opérateur d’assignation de copie accessible et non ambigu.Une variable spécifiée dans la clause
copyin
doit être une variablethreadprivate
.
2.7.2.8 copyprivate
La clause copyprivate
fournit un mécanisme permettant d’utiliser une variable privée pour diffuser une valeur d’un membre d’une équipe vers les autres membres. Il s’agit d’une alternative à l’utilisation d’une variable partagée pour la valeur lorsqu’une telle variable partagée est fournie (par exemple, dans une récursivité nécessitant une variable différente à chaque niveau). La clause copyprivate
ne peut apparaître que dans la directive single
.
La syntaxe de la clause copyprivate
est la suivante :
copyprivate(
variable-list
)
L’effet de la clause copyprivate
sur les variables de sa liste variable-list se produit après l’exécution du bloc structuré associé à la construction single
, et avant que les threads de l’équipe n’aient laissé la barrière à la fin de la construction. Ensuite, dans tous les autres threads de l’équipe, chaque variable de la liste variable-list est définie (comme si par affectation) avec la valeur de la variable correspondante dans le thread qui a exécuté le bloc structuré de la construction.
Voici les restrictions à la clause copyprivate
:
Une variable spécifiée dans la clause
copyprivate
ne doit pas apparaître dans une clauseprivate
oufirstprivate
pour la même directivesingle
.Si une directive
single
avec une clausecopyprivate
est rencontrée dans l’étendue dynamique d’une région parallèle, toutes les variables spécifiées dans la clausecopyprivate
doivent être privées dans le contexte englobant.Une variable spécifiée dans la clause
copyprivate
doit avoir un opérateur d’assignation de copie accessible et non ambigu.
2.8 Liaison de directives
La liaison dynamique de directives doit respecter les règles suivantes :
Les directives
for
,sections
,single
,master
etbarrier
sont liées auparallel
englobant dynamiquement, se celui-ci existe, quelle que soit la valeur d’une clauseif
éventuellement présente sur cette directive. Si aucune région parallèle n’est en cours d’exécution, les directives sont exécutées par une équipe composée uniquement du thread principal.La directive
ordered
est liée aufor
englobant dynamiquement.La directive
atomic
applique l’accès exclusif vis-à-vis des directivesatomic
dans tous les threads, pas seulement l’équipe actuelle.La directive
critical
applique l’accès exclusif vis-à-vis des directivescritical
dans tous les threads, pas seulement l’équipe actuelle.Une directive ne peut jamais se lier à une directive en dehors du plus proche
parallel
englobant dynamiquement.
2.9 Imbrication de directives
L’imbrication dynamique de directives doit respecter les règles suivantes :
Une directive
parallel
dynamiquement à l’intérieur d’une autreparallel
établit logiquement une nouvelle équipe, composée uniquement du thread actuel, sauf si le parallélisme imbriqué est activé.Les directives
for
,sections
etsingle
qui se lient à la mêmeparallel
ne sont pas autorisées à s’imbriquer entre elles.Les directives
critical
portant le même nom ne sont pas autorisées à s’imbriquer entre elles. Notez que cette restriction n’est pas suffisante pour empêcher le blocage.Les directives
for
,sections
etsingle
ne sont pas autorisées dans l’étendue dynamique des régionscritical
,ordered
etmaster
si les directives sont liées au mêmeparallel
que les régions.Les directives
barrier
ne sont pas autorisées dans l’étendue dynamique des régionsfor
,ordered
,sections
,single
,master
etcritical
si les directives sont liées au mêmeparallel
que les régions.Les directives
master
ne sont pas autorisées dans l’étendue dynamique des régionsfor
,sections
etsingle
si les directivesmaster
sont liées au mêmeparallel
que les directives de partage de travail.Les directives
ordered
ne sont pas autorisées dans l’étendue dynamique des régionscritical
si les directives sont liées au mêmeparallel
que les régions.Toute directive autorisée lorsqu’elle est exécutée dynamiquement à l’intérieur d’une région parallèle est également autorisée lorsqu’elle est exécutée en dehors d’une région parallèle. Lorsqu’elle est exécutée dynamiquement en dehors d’une région parallèle spécifiée par l’utilisateur, la directive est exécutée par une équipe composée uniquement du thread principal.