2. Direttive
Le direttive sono basate su #pragma
direttive definite negli standard C e C++. I compilatori che supportano l'API OpenMP C e C++ includeranno un'opzione della riga di comando che attiva e consente l'interpretazione di tutte le direttive del compilatore OpenMP.
2.1 Formato direttiva
La sintassi di una direttiva OpenMP viene formalmente specificata dalla grammatica nell'appendice C e in modo informale come indicato di seguito:
#pragma omp directive-name [clause[ [,] clause]...] new-line
Ogni direttiva inizia con #pragma omp
, per ridurre il potenziale di conflitto con altre direttive pragma (non OpenMP o vendor extensions to OpenMP) con gli stessi nomi. Il resto della direttiva segue le convenzioni degli standard C e C++ per le direttive del compilatore. In particolare, è possibile usare spazi vuoti prima e dopo #
, e a volte è necessario usare gli spazi vuoti per separare le parole in una direttiva. I token di pre-elaborazione che seguono #pragma omp
sono soggetti alla sostituzione delle macro.
Le direttive fanno distinzione tra maiuscole e minuscole. L'ordine in cui le clausole vengono visualizzate nelle direttive non è significativo. Le clausole sulle direttive possono essere ripetute in base alle esigenze, soggette alle restrizioni elencate nella descrizione di ogni clausola. Se variabile-list viene visualizzato in una clausola , deve specificare solo le variabili. È possibile specificare un solo nome di direttiva per direttiva. Ad esempio, la direttiva seguente non è consentita:
/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier
Una direttiva OpenMP si applica al massimo a un'istruzione riuscita, che deve essere un blocco strutturato.
2.2 Compilazione condizionale
Il _OPENMP
nome della macro è definito dalle implementazioni conformi a OpenMP come costante decimale aaaamm, che sarà l'anno e il mese della specifica approvata. Questa macro non deve essere oggetto di una #define
#undef
direttiva di pre-elaborazione o .
#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif
Se i fornitori definiscono le estensioni per OpenMP, possono specificare macro predefinite aggiuntive.
2.3 costrutto parallelo
La direttiva seguente definisce un'area parallela, ovvero un'area del programma che deve essere eseguita da molti thread in parallelo. Questa direttiva è il costrutto fondamentale che avvia l'esecuzione parallela.
#pragma omp parallel [clause[ [, ]clause] ...] new-line structured-block
La clausola è una delle seguenti:
if(
scalar-expression)
private(
variable-list)
firstprivate(
variable-list)
default(shared | none)
shared(
variable-list)
copyin(
variable-list)
reduction(
operatore:
variable-list)
num_threads(
integer-expression)
Quando un thread arriva a un costrutto parallelo, viene creato un team di thread se uno dei casi seguenti è true:
- Nessuna
if
clausola è presente. - L'espressione
if
restituisce un valore diverso da zero.
Questo thread diventa il thread master del team, con un numero di thread pari a 0 e tutti i thread del team, incluso il thread master, eseguono l'area in parallelo. Se il valore dell'espressione if
è zero, l'area viene serializzata.
Per determinare il numero di thread richiesti, le regole seguenti verranno considerate in ordine. Verrà applicata la prima regola la cui condizione viene soddisfatta:
Se la
num_threads
clausola è presente, il valore dell'espressione integer è il numero di thread richiesti.Se la funzione di
omp_set_num_threads
libreria è stata chiamata, il valore dell'argomento nella chiamata eseguita più di recente è il numero di thread richiesti.Se la variabile
OMP_NUM_THREADS
di ambiente è definita, il valore di questa variabile di ambiente è il numero di thread richiesti.Se non viene usato nessuno dei metodi precedenti, il numero di thread richiesti è definito dall'implementazione.
Se la num_threads
clausola è presente, sostituisce il numero di thread richiesti dalla funzione di omp_set_num_threads
libreria o dalla OMP_NUM_THREADS
variabile di ambiente solo per l'area parallela a cui viene applicato. Le aree parallele successive non ne sono interessate.
Il numero di thread che eseguono l'area parallela dipende anche dal fatto che la regolazione dinamica del numero di thread sia abilitata. Se la regolazione dinamica è disabilitata, il numero richiesto di thread eseguirà l'area parallela. Se la regolazione dinamica è abilitata, il numero richiesto di thread è il numero massimo di thread che possono eseguire l'area parallela.
Se viene rilevata un'area parallela durante la regolazione dinamica del numero di thread è disabilitata e il numero di thread richiesti per l'area parallela è maggiore del numero che il sistema di runtime può fornire, il comportamento del programma è definito dall'implementazione. Un'implementazione può, ad esempio, interrompere l'esecuzione del programma oppure serializzare l'area parallela.
La omp_set_dynamic
funzione di libreria e la OMP_DYNAMIC
variabile di ambiente possono essere usate per abilitare e disabilitare la regolazione dinamica del numero di thread.
Il numero di processori fisici che ospitano effettivamente i thread in qualsiasi momento è definito dall'implementazione. Una volta creato, il numero di thread nel team rimane costante per la durata di tale area parallela. Può essere modificato in modo esplicito dall'utente o automaticamente dal sistema di runtime da un'area parallela a un'altra.
Le istruzioni contenute nell'extent dinamico dell'area parallela vengono eseguite da ogni thread e ogni thread può eseguire un percorso di istruzioni diverse dagli altri thread. Le direttive rilevate al di fuori dell'estensione lessicale di un'area parallela vengono definite direttive orfane.
C'è una barriera implicita alla fine di un'area parallela. Solo il thread master del team continua l'esecuzione alla fine di un'area parallela.
Se un thread in un team che esegue un'area parallela incontra un altro costrutto parallelo, crea un nuovo team e diventa il master del nuovo team. Le aree parallele annidate vengono serializzate per impostazione predefinita. Di conseguenza, per impostazione predefinita, un'area parallela annidata viene eseguita da un team composto da un thread. Il comportamento predefinito può essere modificato usando la funzione omp_set_nested
della libreria di runtime o la variabile OMP_NESTED
di ambiente . Tuttavia, il numero di thread in un team che esegue un'area parallela annidata è definito dall'implementazione.
Le restrizioni alla parallel
direttiva sono le seguenti:
Al massimo, una
if
clausola può essere visualizzata nella direttiva .Non è specificato se si verificano effetti collaterali all'interno dell'espressione o
num_threads
dell'espressione.Un
throw
oggetto eseguito all'interno di un'area parallela deve causare la ripresa dell'esecuzione all'interno dell'extent dinamico dello stesso blocco strutturato e deve essere intercettata dallo stesso thread che ha generato l'eccezione.Solo una singola
num_threads
clausola può essere visualizzata nella direttiva . L'espressionenum_threads
viene valutata al di fuori del contesto dell'area parallela e deve restituire un valore intero positivo.L'ordine di valutazione delle
if
clausole enum_threads
non è specificato.
Riferimenti incrociati
private
Clausole ,firstprivate
ereduction
(sezione 2.7.2)default
shared
copyin
- OMP_NUM_THREADS variabile di ambiente
- funzione di libreria omp_set_dynamic
- OMP_DYNAMIC variabile di ambiente
- funzione omp_set_nested
- OMP_NESTED variabile di ambiente
- funzione della libreria omp_set_num_threads
2.4 Costrutti di condivisione del lavoro
Un costrutto di condivisione di lavoro distribuisce l'esecuzione dell'istruzione associata tra i membri del team che lo incontrano. Le direttive di condivisione di lavoro non avviano nuovi thread e non esiste alcuna barriera implicita all'ingresso di un costrutto di condivisione di lavoro.
La sequenza di costrutti e barrier
direttive di condivisione di lavoro rilevati deve essere la stessa per ogni thread in un team.
OpenMP definisce i costrutti di condivisione di lavoro seguenti e questi costrutti sono descritti nelle sezioni seguenti:
2.4.1 per costrutto
La for
direttiva identifica un costrutto iterativo di condivisione del lavoro che specifica che le iterazioni del ciclo associato verranno eseguite in parallelo. Le iterazioni del for
ciclo vengono distribuite tra thread già esistenti nel team che eseguono il costrutto parallelo a cui viene associato. La sintassi del for
costrutto è la seguente:
#pragma omp for [clause[[,] clause] ... ] new-line for-loop
La clausola è una delle seguenti:
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
operatore:
variable-list)
ordered
schedule(
kind [,
chunk_size])
nowait
La for
direttiva pone restrizioni sulla struttura del ciclo corrispondente for
. In particolare, il ciclo corrispondente for
deve avere una forma canonica:
for (
init-expr ;
var logical-op b ;
incr-expr )
init-expr
Uno dei seguenti:
- var = lb
- integer-type var = lb
incr-expr
Uno dei seguenti:
++
Var- Var
++
--
Var- Var
--
- var
+=
incr - var
-=
incr - var var
+
=
incr - var
=
incr+
var - var var
-
=
incr
var
Variabile integer con segno. Se questa variabile verrebbe altrimenti condivisa, viene resa implicitamente privata per la durata dell'oggetto for
. Non modificare questa variabile all'interno del corpo dell'istruzione for
. A meno che non venga specificata lastprivate
la variabile , il relativo valore dopo che il ciclo è indeterminato.
op logico
Uno dei seguenti:
<
<=
>
>=
lb, b e incr
Espressioni integer invarianti del ciclo. Non esiste alcuna sincronizzazione durante la valutazione di queste espressioni, quindi gli effetti collaterali valutati producono risultati indeterminati.
Il formato canonico consente di calcolare il numero di iterazioni del ciclo all'interno del ciclo. Questo calcolo viene eseguito con valori nel tipo di var, dopo promozioni integrali. In particolare, se il valore di b -
lb +
incr non può essere rappresentato in tale tipo, il risultato è indeterminato. Inoltre, se logical-op è <
o <=
, incr-expr deve causare l'aumento di var per ogni iterazione del ciclo. Se logical-op è >
o >=
, incr-expr deve fare in modo che var venga ridotto in ogni iterazione del ciclo.
La schedule
clausola specifica il modo in cui le iterazioni del for
ciclo vengono divise tra i thread del team. La correttezza di un programma non deve dipendere da quale thread esegue una particolare iterazione. Il valore di chunk_size, se specificato, deve essere un'espressione integer invariante di ciclo con un valore positivo. Non esiste alcuna sincronizzazione durante la valutazione di questa espressione, quindi gli effetti collaterali valutati producono risultati indeterminati. Il tipo di pianificazione può essere uno dei valori seguenti:
Tabella 2-1: schedule
valori del tipo di clausola
valore | Descrizione |
---|---|
static | Quando schedule(static, si specifica chunk_size) , le iterazioni vengono suddivise in blocchi di dimensioni specificate da chunk_size. I blocchi vengono assegnati in modo statico ai thread del team in modo round robin nell'ordine del numero di thread. Quando non viene specificato alcun chunk_size , lo spazio di iterazione viene diviso in blocchi di dimensioni approssimativamente uguali, con un blocco assegnato a ogni thread. |
dynamic | Quando schedule(dynamic, si specifica chunk_size) , le iterazioni vengono suddivise in una serie di blocchi, ognuno contenente chunk_size iterazioni. Ogni blocco viene assegnato a un thread in attesa di un'assegnazione. Il thread esegue il blocco di iterazioni e quindi attende l'assegnazione successiva fino a quando non rimangono assegnati blocchi. L'ultimo blocco da assegnare può avere un numero minore di iterazioni. Quando non viene specificato alcun chunk_size , il valore predefinito è 1. |
Guidato | Quando schedule(guided, si specifica chunk_size) , le iterazioni vengono assegnate ai thread in blocchi con dimensioni decrescenti. Quando un thread termina il blocco assegnato di iterazioni, viene assegnato dinamicamente un altro blocco fino a quando non viene lasciato nessuno. Per un chunk_size di 1, la dimensione di ogni blocco è approssimativamente il numero di iterazioni non firmate divise per il numero di thread. Queste dimensioni diminuiscono quasi in modo esponenziale a 1. Per un chunk_size con valore k maggiore di 1, le dimensioni diminuiscono quasi in modo esponenziale a k, ad eccezione del fatto che l'ultimo blocco può avere meno di k iterazioni. Quando non viene specificato alcun chunk_size , il valore predefinito è 1. |
runtime | Quando schedule(runtime) viene specificato, la decisione relativa alla pianificazione viene posticipata fino al runtime. Il tipo di pianificazione e le dimensioni dei blocchi possono essere scelti in fase di esecuzione impostando la variabile OMP_SCHEDULE di ambiente . Se questa variabile di ambiente non è impostata, la pianificazione risultante è definita dall'implementazione. Se schedule(runtime) viene specificato, chunk_size non deve essere specificato. |
In assenza di una clausola definita schedule
in modo esplicito, il valore predefinito schedule
è definito dall'implementazione.
Un programma conforme a OpenMP non deve basarsi su una pianificazione specifica per l'esecuzione corretta. Un programma non deve basarsi su un tipo di pianificazione conforme esattamente alla descrizione indicata in precedenza, perché è possibile avere variazioni nelle implementazioni dello stesso tipo di pianificazione in compilatori diversi. Le descrizioni possono essere usate per selezionare la pianificazione appropriata per una determinata situazione.
La ordered
clausola deve essere presente quando ordered
le direttive si associano al for
costrutto.
C'è una barriera implicita alla fine di un for
costrutto, a meno che non venga specificata una nowait
clausola.
Le restrizioni alla for
direttiva sono le seguenti:
Il
for
ciclo deve essere un blocco strutturato e, inoltre, l'esecuzione non deve essere terminata da un'istruzionebreak
.I valori delle espressioni di controllo del ciclo del
for
ciclo associato a unafor
direttiva devono essere uguali per tutti i thread del team.La
for
variabile di iterazione del ciclo deve avere un tipo integer con segno.In una direttiva può essere visualizzata solo una
for
singolaschedule
clausola.In una direttiva può essere visualizzata solo una
for
singolaordered
clausola.In una direttiva può essere visualizzata solo una
for
singolanowait
clausola.Non è specificato se o con quale frequenza si verificano effetti collaterali all'interno delle espressioni chunk_size, lb, b o incr .
Il valore dell'espressione chunk_size deve essere lo stesso per tutti i thread del team.
Riferimenti incrociati
private
Clausole ,firstprivate
lastprivate
, ereduction
(sezione 2.7.2)- OMP_SCHEDULE variabile di ambiente
- costrutto ordinato
- clausola schedule
2.4.2 Costrutto sezioni
La sections
direttiva identifica un costrutto di condivisione del lavoro non determinitivo che specifica un set di costrutti da dividere tra i thread di un team. Ogni sezione viene eseguita una volta da un thread nel team. La sintassi della sections
direttiva è la seguente:
#pragma omp sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
La clausola è una delle seguenti:
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
operatore:
variable-list)
nowait
Ogni sezione è preceduta da una section
direttiva, anche se la section
direttiva è facoltativa per la prima sezione. Le section
direttive devono essere visualizzate all'interno dell'estensione lessicale della sections
direttiva. C'è una barriera implicita alla fine di un sections
costrutto, a meno che non venga specificato un oggetto nowait
.
Le restrizioni alla sections
direttiva sono le seguenti:
Una
section
direttiva non deve apparire al di fuori dell'estensione lessicale dellasections
direttiva.In una direttiva può essere visualizzata solo una
sections
singolanowait
clausola.
Riferimenti incrociati
private
Clausole ,firstprivate
lastprivate
, ereduction
(sezione 2.7.2)
2.4.3 costrutto singolo
La single
direttiva identifica un costrutto che specifica che il blocco strutturato associato viene eseguito da un solo thread nel team (non necessariamente il thread master). La sintassi della single
direttiva è la seguente:
#pragma omp single [clause[[,] clause] ...] new-linestructured-block
La clausola è una delle seguenti:
private(
variable-list)
firstprivate(
variable-list)
copyprivate(
variable-list)
nowait
Dopo il single
costrutto è presente una barriera implicita, a meno che non venga specificata una nowait
clausola.
Le restrizioni alla single
direttiva sono le seguenti:
- In una direttiva può essere visualizzata solo una
single
singolanowait
clausola. - La
copyprivate
clausola non deve essere utilizzata con lanowait
clausola .
Riferimenti incrociati
private
Clausole ,firstprivate
ecopyprivate
(sezione 2.7.2)
2.5 Costrutti di condivisione parallela parallela combinati
I costrutti di condivisione di lavoro paralleli combinati sono collegamenti per specificare un'area parallela con un solo costrutto di condivisione di lavoro. La semantica di queste direttive equivale a specificare in modo esplicito una parallel
direttiva seguita da un singolo costrutto di condivisione di lavoro.
Le sezioni seguenti descrivono i costrutti di condivisione di lavoro paralleli combinati:
2.5.1 parallelo per costrutto
La parallel for
direttiva è un collegamento per un'area parallel
che contiene solo una singola for
direttiva. La sintassi della parallel for
direttiva è la seguente:
#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop
Questa direttiva consente tutte le clausole della parallel
direttiva e della for
direttiva, ad eccezione della nowait
clausola , con significati e restrizioni identici. La semantica equivale a specificare in modo esplicito una parallel
direttiva immediatamente seguita da una for
direttiva.
Riferimenti incrociati
2.5.2 Costrutto sezioni parallele
La parallel sections
direttiva fornisce un modulo di scelta rapida per specificare un'area parallel
con una sola sections
direttiva. La semantica equivale a specificare in modo esplicito una parallel
direttiva immediatamente seguita da una sections
direttiva. La sintassi della parallel sections
direttiva è la seguente:
#pragma omp parallel sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
La clausola può essere una delle clausole accettate dalle parallel
direttive e sections
, ad eccezione della nowait
clausola .
Riferimenti incrociati
2.6 Direttive master e di sincronizzazione
Le sezioni seguenti descrivono:
- costrutto master
- costrutto critico
- direttiva barriera
- costrutto atomico
- direttiva flush
- costrutto ordinato
2.6.1 Costrutto master
La master
direttiva identifica un costrutto che specifica un blocco strutturato eseguito dal thread master del team. La sintassi della master
direttiva è la seguente:
#pragma omp master new-linestructured-block
Gli altri thread del team non eseguono il blocco strutturato associato. Non esiste alcuna barriera implicita all'ingresso o all'uscita dal costrutto master.
2.6.2 Costrutto critico
La critical
direttiva identifica un costrutto che limita l'esecuzione del blocco strutturato associato a un singolo thread alla volta. La sintassi della critical
direttiva è la seguente:
#pragma omp critical [(name)] new-linestructured-block
È possibile usare un nome facoltativo per identificare l'area critica. Gli identificatori usati per identificare un'area critica hanno un collegamento esterno e si trovano in uno spazio dei nomi separato dagli spazi dei nomi usati da etichette, tag, membri e identificatori ordinari.
Un thread attende all'inizio di un'area critica fino a quando nessun altro thread non esegue un'area critica (ovunque nel programma) con lo stesso nome. Tutte le direttive senza critical
nome eseguono il mapping allo stesso nome non specificato.
Direttiva barriera 2.6.3
La barrier
direttiva sincronizza tutti i thread in un team. Quando viene rilevato, ogni thread del team attende fino a raggiungere questo punto. La sintassi della barrier
direttiva è la seguente:
#pragma omp barrier new-line
Dopo che tutti i thread del team hanno rilevato la barriera, ogni thread del team inizia a eseguire le istruzioni dopo la direttiva barriera in parallelo. Poiché la barrier
direttiva non ha un'istruzione del linguaggio C come parte della sintassi, esistono alcune restrizioni sul posizionamento all'interno di un programma. Per altre informazioni sulla grammatica formale, vedere appendice C. L'esempio seguente illustra queste restrizioni.
/* 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 costrutto atomico
La atomic
direttiva garantisce che una posizione di memoria specifica venga aggiornata in modo atomico, anziché esponerla alla possibilità di più thread di scrittura simultanei. La sintassi della atomic
direttiva è la seguente:
#pragma omp atomic new-lineexpression-stmt
L'istruzione expression deve avere uno dei formati seguenti:
- x binop
=
expr - x
++
++
x- x
--
--
x
Nelle espressioni precedenti:
x è un'espressione lvalue con tipo scalare.
expr è un'espressione con tipo scalare e non fa riferimento all'oggetto designato da x.
binop non è un operatore di overload ed è uno di
+
,*
,-
,^
&
|
/
,<<
, o .>>
Anche se è definito dall'implementazione se un'implementazione sostituisce tutte le atomic
direttive con critical
direttive con lo stesso nome univoco, la atomic
direttiva consente una migliore ottimizzazione. Spesso sono disponibili istruzioni hardware che possono eseguire l'aggiornamento atomico con il minor sovraccarico.
Solo il carico e l'archivio dell'oggetto designato da x sono atomici. La valutazione di expr non è atomica. Per evitare condizioni di gara, tutti gli aggiornamenti della posizione in parallelo devono essere protetti con la atomic
direttiva, ad eccezione di quelli che sono noti per essere liberi di condizioni di gara.
Le restrizioni alla atomic
direttiva sono le seguenti:
- Tutti i riferimenti atomici alla posizione di archiviazione x in tutto il programma devono avere un tipo compatibile.
Esempi
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 direttiva flush
La flush
direttiva, esplicita o implicita, specifica un punto di sequenza "cross-thread" in cui è necessaria l'implementazione per garantire che tutti i thread di un team abbiano una visualizzazione coerente di determinati oggetti (specificati di seguito) in memoria. Ciò significa che le valutazioni precedenti delle espressioni che fanno riferimento a tali oggetti sono complete e le valutazioni successive non sono ancora state avviate. Ad esempio, i compilatori devono ripristinare i valori degli oggetti dai registri alla memoria e l'hardware potrebbe dover scaricare buffer di scrittura in memoria e ricaricare i valori degli oggetti dalla memoria.
La sintassi della flush
direttiva è la seguente:
#pragma omp flush [(variable-list)] new-line
Se gli oggetti che richiedono la sincronizzazione possono essere designati da variabili, tali variabili possono essere specificate nell'elenco variabile facoltativo. Se un puntatore è presente nell'elenco di variabili, il puntatore stesso viene scaricato, non l'oggetto a cui fa riferimento il puntatore.
Una flush
direttiva senza un elenco di variabili sincronizza tutti gli oggetti condivisi ad eccezione di oggetti inaccessibili con durata di archiviazione automatica. È probabile che si verifichi un sovraccarico maggiore rispetto a un flush
oggetto con un elenco di variabili. Una flush
direttiva senza un elenco di variabili è implicita per le direttive seguenti:
barrier
- All'ingresso e all'uscita da
critical
- All'ingresso e all'uscita da
ordered
- All'ingresso e all'uscita da
parallel
- All'uscita da
for
- All'uscita da
sections
- All'uscita da
single
- All'ingresso e all'uscita da
parallel for
- All'ingresso e all'uscita da
parallel sections
La direttiva non è implicita se è presente una nowait
clausola . Si noti che la flush
direttiva non è implicita per uno dei seguenti elementi:
- All'ingresso a
for
- All'ingresso o all'uscita da
master
- All'ingresso a
sections
- All'ingresso a
single
Un riferimento che accede al valore di un oggetto con un tipo qualificato volatile si comporta come se fosse presente una flush
direttiva che specifica l'oggetto nel punto di sequenza precedente. Un riferimento che modifica il valore di un oggetto con un tipo qualificato volatile si comporta come se esistesse una flush
direttiva che specifica l'oggetto nel punto di sequenza successivo.
Poiché la flush
direttiva non ha un'istruzione del linguaggio C come parte della sintassi, esistono alcune restrizioni sul posizionamento all'interno di un programma. Per altre informazioni sulla grammatica formale, vedere appendice C. L'esempio seguente illustra queste restrizioni.
/* 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)
}
Le restrizioni alla flush
direttiva sono le seguenti:
- Una variabile specificata in una
flush
direttiva non deve avere un tipo riferimento.
2.6.6 costrutto ordinato
Il blocco strutturato che segue una ordered
direttiva viene eseguito nell'ordine in cui le iterazioni verrebbero eseguite in un ciclo sequenziale. La sintassi della ordered
direttiva è la seguente:
#pragma omp ordered new-linestructured-block
Una ordered
direttiva deve trovarsi all'interno dell'extent dinamico di un for
costrutto o parallel for
. La for
direttiva o parallel for
a cui il ordered
costrutto è associato deve avere una ordered
clausola specificata come descritto nella sezione 2.4.1. Nell'esecuzione di un for
costrutto o parallel for
con una ordered
clausola , ordered
i costrutti vengono eseguiti rigorosamente nell'ordine in cui verrebbero eseguiti in un'esecuzione sequenziale del ciclo.
Le restrizioni alla ordered
direttiva sono le seguenti:
- Un'iterazione di un ciclo con un
for
costrutto non deve eseguire più volte la stessa direttiva ordinata e non deve eseguire più di unaordered
direttiva.
2.7 Ambiente dati
Questa sezione presenta una direttiva e diverse clausole per controllare l'ambiente dati durante l'esecuzione di aree parallele, come indicato di seguito:
Viene fornita una direttiva threadprivate per creare variabili di ambito file, ambito dello spazio dei nomi o variabili di ambito blocco statico locali in un thread.
Le clausole che possono essere specificate nelle direttive per controllare gli attributi di condivisione delle variabili per la durata dei costrutti paralleli o di condivisione di lavoro sono descritte nella sezione 2.7.2.
2.7.1 direttiva threadprivate
La threadprivate
direttiva rende le variabili di ambito file, spazio dei nomi o ambito di blocco statico specificate nell'elenco di variabili private in un thread. variable-list è un elenco delimitato da virgole di variabili che non hanno un tipo incompleto. La sintassi della threadprivate
direttiva è la seguente:
#pragma omp threadprivate(variable-list) new-line
Ogni copia di una threadprivate
variabile viene inizializzata una volta, in un punto non specificato nel programma prima del primo riferimento a tale copia e nel modo consueto (ad esempio, come la copia master verrebbe inizializzata in un'esecuzione seriale del programma). Si noti che se si fa riferimento a un oggetto in un inizializzatore esplicito di una threadprivate
variabile e il valore dell'oggetto viene modificato prima del primo riferimento a una copia della variabile, il comportamento non viene specificato.
Come per qualsiasi variabile privata, un thread non deve fare riferimento alla copia di un altro thread di un threadprivate
oggetto. Durante le aree seriali e le aree master del programma, i riferimenti saranno alla copia del thread master dell'oggetto.
Dopo l'esecuzione della prima area parallela, i dati negli threadprivate
oggetti vengono mantenuti solo se il meccanismo dei thread dinamici è stato disabilitato e se il numero di thread rimane invariato per tutte le aree parallele.
Le restrizioni alla threadprivate
direttiva sono le seguenti:
Una
threadprivate
direttiva per le variabili di ambito file o di ambito dello spazio dei nomi deve essere visualizzata all'esterno di qualsiasi definizione o dichiarazione e deve precedere lessicalmente tutti i riferimenti a una delle variabili nel relativo elenco.Ogni variabile nell'elenco di variabili di una
threadprivate
direttiva nell'ambito del file o dello spazio dei nomi deve fare riferimento a una dichiarazione di variabile nell'ambito del file o dello spazio dei nomi che precede in modo lessicale la direttiva.Una
threadprivate
direttiva per le variabili di ambito di blocco statico deve essere visualizzata nell'ambito della variabile e non in un ambito annidato. La direttiva deve precedere lessicalmente tutti i riferimenti a una qualsiasi delle variabili nel relativo elenco.Ogni variabile nell'elenco di variabili di una
threadprivate
direttiva nell'ambito del blocco deve fare riferimento a una dichiarazione di variabile nello stesso ambito che precede lessicalmente la direttiva. La dichiarazione di variabile deve usare l'identificatore di classe di archiviazione statica.Se una variabile viene specificata in una direttiva in un'unità
threadprivate
di conversione, deve essere specificata in unathreadprivate
direttiva in ogni unità di conversione in cui viene dichiarata.Una
threadprivate
variabile non deve essere visualizzata in alcuna clausola, ad eccezione dellaif
copyin
clausola ,copyprivate
schedule
,num_threads
, o .L'indirizzo di una
threadprivate
variabile non è una costante di indirizzo.Una
threadprivate
variabile non deve avere un tipo incompleto o un tipo riferimento.Una
threadprivate
variabile con tipo di classe non POD deve avere un costruttore di copia accessibile e non ambiguo se viene dichiarato con un inizializzatore esplicito.
Nell'esempio seguente viene illustrato come modificare una variabile visualizzata in un inizializzatore può causare un comportamento non specificato e come evitare questo problema usando un oggetto ausiliario e un costruttore di copia.
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. */
}
}
Riferimenti incrociati
- thread dinamici
- OMP_DYNAMIC variabile di ambiente
2.7.2 Clausole degli attributi di condivisione dei dati
Diverse direttive accettano clausole che consentono a un utente di controllare gli attributi di condivisione delle variabili per la durata dell'area. Le clausole degli attributi di condivisione si applicano solo alle variabili nell'estensione lessicale della direttiva in cui viene visualizzata la clausola . Non tutte le clausole seguenti sono consentite per tutte le direttive. L'elenco di clausole valide per una specifica direttiva è descritta con la direttiva .
Se una variabile è visibile quando viene rilevato un costrutto parallelo o di condivisione di lavoro e la variabile non viene specificata in una clausola o threadprivate
direttiva dell'attributo di condivisione, la variabile viene condivisa. Le variabili statiche dichiarate all'interno dell'extent dinamico di un'area parallela vengono condivise. La memoria allocata dell'heap (ad esempio, l'uso malloc()
in C o C++ o l'operatore new
in C++) viene condivisa. Il puntatore a questa memoria, tuttavia, può essere privato o condiviso. Le variabili con durata di archiviazione automatica dichiarata all'interno dell'extent dinamico di un'area parallela sono private.
La maggior parte delle clausole accetta un argomento variable-list , ovvero un elenco delimitato da virgole di variabili visibili. Se una variabile a cui viene fatto riferimento in una clausola di attributo di condivisione dati ha un tipo derivato da un modello e non sono presenti altri riferimenti a tale variabile nel programma, il comportamento non è definito.
Tutte le variabili visualizzate all'interno delle clausole di direttiva devono essere visibili. Le clausole possono essere ripetute in base alle esigenze, ma non è possibile specificare alcuna variabile in più clausole, ad eccezione del fatto che una variabile può essere specificata sia in una firstprivate
clausola che in una lastprivate
clausola .
Le sezioni seguenti descrivono le clausole degli attributi di condivisione dei dati:
2.7.2.1 private
La private
clausola dichiara che le variabili in variable-list devono essere private per ogni thread in un team. La sintassi della private
clausola è la seguente:
private(variable-list)
Il comportamento di una variabile specificata in una private
clausola è il seguente. Per il costrutto viene allocato un nuovo oggetto con durata di archiviazione automatica. Le dimensioni e l'allineamento del nuovo oggetto sono determinati dal tipo della variabile. Questa allocazione si verifica una volta per ogni thread del team e viene richiamato un costruttore predefinito per un oggetto classe, se necessario; in caso contrario, il valore iniziale è indeterminato. L'oggetto originale a cui fa riferimento la variabile ha un valore indeterminato al momento dell'immissione del costrutto, non deve essere modificato all'interno dell'extent dinamico del costrutto e ha un valore indeterminato all'uscita dal costrutto.
Nell'extent lessicale del costrutto di direttiva, la variabile fa riferimento al nuovo oggetto privato allocato dal thread.
Le restrizioni per la private
clausola sono le seguenti:
Una variabile con un tipo di classe specificato in una
private
clausola deve avere un costruttore predefinito accessibile e non ambiguo.Una variabile specificata in una
private
clausola non deve avere unconst
tipo qualificato a meno che non abbia un tipo di classe con unmutable
membro.Una variabile specificata in una
private
clausola non deve avere un tipo incompleto o un tipo riferimento.Le variabili visualizzate nella
reduction
clausola di unaparallel
direttiva non possono essere specificate in una clausola in unaprivate
direttiva di condivisione di lavoro che viene associata al costrutto parallelo.
2.7.2.2 firstprivate
La firstprivate
clausola fornisce un superset delle funzionalità fornite dalla private
clausola . La sintassi della firstprivate
clausola è la seguente:
firstprivate(variable-list)
Le variabili specificate in variable-list hanno private
semantica della clausola, come descritto nella sezione 2.7.2.1. L'inizializzazione o la costruzione avviene come se fosse stata eseguita una volta per ogni thread, prima dell'esecuzione del costrutto del thread. Per una firstprivate
clausola su un costrutto parallelo, il valore iniziale del nuovo oggetto privato è il valore dell'oggetto originale esistente immediatamente prima del costrutto parallelo per il thread che lo rileva. Per una firstprivate
clausola su un costrutto di condivisione di lavoro, il valore iniziale del nuovo oggetto privato per ogni thread che esegue il costrutto di condivisione di lavoro è il valore dell'oggetto originale esistente prima del momento in cui lo stesso thread rileva il costrutto di condivisione di lavoro. Inoltre, per gli oggetti C++, il nuovo oggetto privato per ogni thread viene copiato dall'oggetto originale.
Le restrizioni per la firstprivate
clausola sono le seguenti:
Una variabile specificata in una
firstprivate
clausola non deve avere un tipo incompleto o un tipo riferimento.Una variabile con un tipo di classe specificato come
firstprivate
deve avere un costruttore di copia accessibile e non ambiguo.Le variabili private all'interno di un'area parallela o visualizzate nella
reduction
clausola di unaparallel
direttiva non possono essere specificate in una clausola in unafirstprivate
direttiva di condivisione di lavoro che viene associata al costrutto parallelo.
2.7.2.3 lastprivate
La lastprivate
clausola fornisce un superset delle funzionalità fornite dalla private
clausola . La sintassi della lastprivate
clausola è la seguente:
lastprivate(variable-list)
Le variabili specificate nella semantica della clausola variable-list hanno private
. Quando viene visualizzata una lastprivate
clausola nella direttiva che identifica un costrutto di condivisione di lavoro, il valore di ogni lastprivate
variabile dell'ultima iterazione sequenziale del ciclo associato o l'ultima direttiva di sezione lessicalmente ultima, viene assegnata all'oggetto originale della variabile. Le variabili non assegnate a un valore dall'ultima iterazione di for
o parallel for
o dall'ultima sezione lessicale della sections
direttiva o parallel sections
hanno valori indeterminato dopo il costrutto. Gli oggetti secondari non assegnati hanno anche un valore indeterminato dopo il costrutto.
Le restrizioni per la lastprivate
clausola sono le seguenti:
Tutte le restrizioni per
private
l'applicazione.Una variabile con un tipo di classe specificato come
lastprivate
deve avere un operatore di assegnazione di copia accessibile e non ambiguo.Le variabili private all'interno di un'area parallela o visualizzate nella
reduction
clausola di unaparallel
direttiva non possono essere specificate in una clausola in unalastprivate
direttiva di condivisione di lavoro che viene associata al costrutto parallelo.
2.7.2.4 shared
Questa clausola condivide le variabili visualizzate nell'elenco di variabili tra tutti i thread di un team. Tutti i thread all'interno di un team accedono alla stessa area di archiviazione per shared
le variabili.
La sintassi della shared
clausola è la seguente:
shared(variable-list)
2.7.2.5 default
La default
clausola consente all'utente di influire sugli attributi di condivisione dei dati delle variabili. La sintassi della default
clausola è la seguente:
default(shared | none)
Specificare default(shared)
equivale a elencare in modo esplicito ogni variabile attualmente visibile in una shared
clausola, a meno che non threadprivate
sia o const
-qualified. In assenza di una clausola esplicita default
, il comportamento predefinito è uguale a quello default(shared)
specificato.
Per specificare default(none)
è necessario che almeno uno dei valori seguenti sia true per ogni riferimento a una variabile nell'extent lessicale del costrutto parallelo:
La variabile è elencata in modo esplicito in una clausola di attributo di condivisione dati di un costrutto che contiene il riferimento.
La variabile viene dichiarata all'interno del costrutto parallelo.
La variabile è
threadprivate
.La variabile ha un
const
tipo completo.La variabile è la variabile di controllo del ciclo per un
for
ciclo che segue immediatamente unafor
direttiva oparallel for
e il riferimento alla variabile viene visualizzato all'interno del ciclo.
Se si specifica una variabile in una firstprivate
clausola , lastprivate
o reduction
di una direttiva racchiusa, viene generato un riferimento implicito alla variabile nel contesto di inclusione. Tali riferimenti impliciti sono soggetti anche ai requisiti elencati in precedenza.
In una direttiva è possibile specificare solo una parallel
singola default
clausola.
È possibile eseguire l'override dell'attributo predefinito di condivisione dei dati di una variabile usando le private
clausole , reduction
firstprivate
lastprivate
, , e shared
, come illustrato nell'esempio seguente:
#pragma omp parallel for default(shared) firstprivate(i)\
private(x) private(r) lastprivate(i)
2.7.2.6 reduction
Questa clausola esegue una riduzione delle variabili scalari visualizzate in variable-list, con l'operatore op. La sintassi della reduction
clausola è la seguente:
reduction(
Op :
variable-list )
Una riduzione viene in genere specificata per un'istruzione con una delle forme seguenti:
- x
=
x op expr - x binop
=
expr - x
=
expr op x (ad eccezione della sottrazione) - x
++
++
x- x
--
--
x
dove:
x
Una delle variabili di riduzione specificate nell'elenco.
variable-list
Elenco delimitato da virgole di variabili di riduzione scalari.
expr
Espressione con tipo scalare che non fa riferimento a x.
op
Non un operatore di overload, ma uno di +
, *
, -
, ^
&
, |
&&
, , o ||
.
binop
Non un operatore di overload, ma uno di +
, *
, &
-
, ^
, o |
.
Di seguito è riportato un esempio della reduction
clausola :
#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];
}
Come illustrato nell'esempio, un operatore può essere nascosto all'interno di una chiamata di funzione. L'utente deve prestare attenzione che l'operatore specificato nella reduction
clausola corrisponda all'operazione di riduzione.
Anche se l'operando destro dell'operatore ||
non ha effetti collaterali in questo esempio, è consentito, ma deve essere usato con attenzione. In questo contesto, può verificarsi un effetto collaterale che non si verifica durante l'esecuzione sequenziale del ciclo durante l'esecuzione parallela. Questa differenza può verificarsi perché l'ordine di esecuzione delle iterazioni è indeterminato.
L'operatore viene usato per determinare il valore iniziale di qualsiasi variabile privata utilizzata dal compilatore per la riduzione e per determinare l'operatore di finalizzazione. Specificando l'operatore in modo esplicito, l'istruzione di riduzione può essere esterna all'extent lessicale del costrutto. È possibile specificare un numero qualsiasi di reduction
clausole nella direttiva, ma una variabile può essere inclusa al massimo in una reduction
clausola per tale direttiva.
Viene creata una copia privata di ogni variabile in variable-list , una per ogni thread, come se fosse stata usata la private
clausola . La copia privata viene inizializzata in base all'operatore (vedere la tabella seguente).
Alla fine dell'area per cui è stata specificata la reduction
clausola , l'oggetto originale viene aggiornato in modo da riflettere il risultato della combinazione del valore originale con il valore finale di ognuna delle copie private usando l'operatore specificato. Gli operatori di riduzione sono tutti associativi (ad eccezione della sottrazione) e il compilatore può riassociare liberamente il calcolo del valore finale. I risultati parziali di una riduzione della sottrazione vengono aggiunti per formare il valore finale.
Il valore dell'oggetto originale diventa indeterminato quando il primo thread raggiunge la clausola contenitore e rimane così fino al completamento del calcolo della riduzione. Normalmente, il calcolo sarà completato alla fine del costrutto; Tuttavia, se la reduction
clausola viene utilizzata su un costrutto a cui nowait
viene applicato anche , il valore dell'oggetto originale rimane indeterminato fino a quando non viene eseguita una sincronizzazione delle barriere per garantire che tutti i thread abbiano completato la reduction
clausola .
Nella tabella seguente sono elencati gli operatori validi e i relativi valori di inizializzazione canonici. Il valore di inizializzazione effettivo sarà coerente con il tipo di dati della variabile di riduzione.
Operatore | Inizializzazione |
---|---|
+ |
0 |
* |
1 |
- |
0 |
& |
~0 |
| |
0 |
^ |
0 |
&& |
1 |
|| |
0 |
Le restrizioni per la reduction
clausola sono le seguenti:
Il tipo delle variabili nella
reduction
clausola deve essere valido per l'operatore di riduzione, ad eccezione del fatto che i tipi di puntatore e i tipi riferimento non sono mai consentiti.Una variabile specificata nella
reduction
clausola non deve essereconst
-qualified.Le variabili private all'interno di un'area parallela o visualizzate nella
reduction
clausola di unaparallel
direttiva non possono essere specificate in una clausola in unareduction
direttiva di condivisione di lavoro che viene associata al costrutto parallelo.#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 copyin
clausola fornisce un meccanismo per assegnare lo stesso valore alle threadprivate
variabili per ogni thread del team che esegue l'area parallela. Per ogni variabile specificata in una copyin
clausola, il valore della variabile nel thread master del team viene copiato, come se fosse assegnato, alle copie private del thread all'inizio dell'area parallela. La sintassi della copyin
clausola è la seguente:
copyin(
variable-list
)
Le restrizioni per la copyin
clausola sono le seguenti:
Una variabile specificata nella
copyin
clausola deve avere un operatore di assegnazione di copia accessibile e non ambiguo.Una variabile specificata nella
copyin
clausola deve essere unathreadprivate
variabile.
2.7.2.8 copyprivate
La copyprivate
clausola fornisce un meccanismo per usare una variabile privata per trasmettere un valore da un membro di un team agli altri membri. È un'alternativa all'uso di una variabile condivisa per il valore quando si specifica una variabile condivisa di questo tipo sarebbe difficile (ad esempio, in una ricorsione che richiede una variabile diversa a ogni livello). La copyprivate
clausola può essere visualizzata solo nella single
direttiva .
La sintassi della copyprivate
clausola è la seguente:
copyprivate(
variable-list
)
L'effetto copyprivate
della clausola sulle variabili nel relativo elenco di variabili si verifica dopo l'esecuzione del blocco strutturato associato al single
costrutto e prima che uno dei thread del team abbia lasciato la barriera alla fine del costrutto. Quindi, in tutti gli altri thread del team, per ogni variabile nell'elenco di variabili, tale variabile diventa definita (come se fosse assegnato) con il valore della variabile corrispondente nel thread che ha eseguito il blocco strutturato del costrutto.
Le restrizioni per la copyprivate
clausola sono le seguenti:
Una variabile specificata nella
copyprivate
clausola non deve essere presente in unaprivate
clausola ofirstprivate
per la stessasingle
direttiva.Se viene rilevata una
single
direttiva con unacopyprivate
clausola nell'extent dinamico di un'area parallela, tutte le variabili specificate nellacopyprivate
clausola devono essere private nel contesto di inclusione.Una variabile specificata nella
copyprivate
clausola deve avere un operatore di assegnazione di copia non ambiguo accessibile.
2.8 Associazione di direttiva
L'associazione dinamica delle direttive deve rispettare le regole seguenti:
Le
for
direttive ,single
sections
master
, , ebarrier
si associano all'oggetto che lo racchiudeparallel
in modo dinamico, se presente, indipendentemente dal valore di qualsiasiif
clausola che può essere presente in tale direttiva. Se non viene attualmente eseguita alcuna area parallela, le direttive vengono eseguite da un team composto solo dal thread master.La
ordered
direttiva viene associata all'oggetto chefor
racchiude dinamicamente .La
atomic
direttiva applica l'accesso esclusivo in relazione alleatomic
direttive in tutti i thread, non solo al team corrente.La
critical
direttiva applica l'accesso esclusivo in relazione allecritical
direttive in tutti i thread, non solo al team corrente.Una direttiva non può mai essere associata ad alcuna direttiva all'esterno del più vicino contenitore
parallel
in modo dinamico.
2.9 Annidamento delle direttive
L'annidamento dinamico delle direttive deve rispettare le regole seguenti:
Una
parallel
direttiva in modo dinamico all'interno di un altroparallel
stabilisce logicamente un nuovo team, composto solo dal thread corrente, a meno che non sia abilitato il parallelismo annidato.for
Le direttive ,sections
esingle
che si associano allo stessoparallel
non possono essere annidate tra loro.critical
le direttive con lo stesso nome non possono essere annidate l'una all'altra. Si noti che questa restrizione non è sufficiente per evitare deadlock.for
Le direttive ,sections
esingle
non sono consentite nell'estensione dinamica dellecritical
aree ,ordered
emaster
se le direttive sono associate alle stesseparallel
aree.barrier
le direttive non sono consentite nell'estensione dinamica dellefor
aree ,ordered
,sections
single
,master
, , ecritical
se le direttive sono associate alle stesseparallel
aree.master
Le direttive non sono consentite nell'estensione dinamica difor
,sections
esingle
se lemaster
direttive si associano alle direttiveparallel
di condivisione del lavoro.ordered
le direttive non sono consentite nell'estensione dinamica dellecritical
aree se le direttive si associano alle stesseparallel
aree.Qualsiasi direttiva consentita quando viene eseguita in modo dinamico all'interno di un'area parallela è consentita anche quando viene eseguita all'esterno di un'area parallela. Quando viene eseguito in modo dinamico all'esterno di un'area parallela specificata dall'utente, la direttiva viene eseguita da un team composto solo dal thread master.