Condividi tramite


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(operator: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:

  1. Se la num_threads clausola è presente, il valore dell'espressione integer è il numero di thread richiesti.

  2. 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.

  3. Se la variabile OMP_NUM_THREADS di ambiente è definita, il valore di questa variabile di ambiente è il numero di thread richiesti.

  4. 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_NESTEDdi 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'espressione num_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 e num_threads non è specificato.

Riferimenti incrociati

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(operator: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 lastprivatela 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 una dimensione specificata 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_SCHEDULEdi 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'istruzione break .

  • I valori delle espressioni di controllo del ciclo del for ciclo associato a una for 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 singola schedule clausola.

  • In una direttiva può essere visualizzata solo una for singola ordered clausola.

  • In una direttiva può essere visualizzata solo una for singola nowait 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

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(operator: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 della sections direttiva.

  • In una direttiva può essere visualizzata solo una sections singola nowait clausola.

Riferimenti incrociati

  • privateClausole , firstprivatelastprivate, e reduction (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 singola nowait clausola.
  • La copyprivate clausola non deve essere utilizzata con la nowait clausola .

Riferimenti incrociati

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:

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 una ordered 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 una threadprivate direttiva in ogni unità di conversione in cui viene dichiarata.

  • Una threadprivate variabile non deve essere visualizzata in alcuna clausola, ad eccezione della ifcopyinclausola , copyprivateschedule, 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

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 un consttipo qualificato a meno che non abbia un tipo di classe con un mutable 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 una parallel direttiva non possono essere specificate in una clausola in una private 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 una parallel direttiva non possono essere specificate in una clausola in una firstprivate 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 foro 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 una parallel direttiva non possono essere specificate in una clausola in una lastprivate 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 consttipo completo.

  • La variabile è la variabile di controllo del ciclo per un for ciclo che segue immediatamente una for direttiva o parallel for e il riferimento alla variabile viene visualizzato all'interno del ciclo.

Se si specifica una variabile in una firstprivateclausola , lastprivateo 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 privateclausole , reductionfirstprivatelastprivate, , 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=xopexpr
  • xbinop=expr
  • x=expropx (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 essere const-qualified.

  • Le variabili private all'interno di un'area parallela o visualizzate nella reduction clausola di una parallel direttiva non possono essere specificate in una clausola in una reduction 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 una threadprivate 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 una private clausola o firstprivate per la stessa single direttiva.

  • Se viene rilevata una single direttiva con una copyprivate clausola nell'extent dinamico di un'area parallela, tutte le variabili specificate nella copyprivate 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 fordirettive , singlesectionsmaster, , e barrier si associano all'oggetto che lo racchiude parallelin modo dinamico, se presente, indipendentemente dal valore di qualsiasi if 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 che forracchiude dinamicamente .

  • La atomic direttiva applica l'accesso esclusivo in relazione alle atomic direttive in tutti i thread, non solo al team corrente.

  • La critical direttiva applica l'accesso esclusivo in relazione alle critical 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 parallelin 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 altro parallel stabilisce logicamente un nuovo team, composto solo dal thread corrente, a meno che non sia abilitato il parallelismo annidato.

  • forLe direttive , sectionse single che si associano allo stesso parallel 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.

  • forLe direttive , sectionse single non sono consentite nell'estensione dinamica delle criticalaree , orderede master se le direttive sono associate alle stesse parallel aree.

  • barrierle direttive non sono consentite nell'estensione dinamica delle foraree , ordered, sectionssingle, master, , e critical se le direttive sono associate alle stesse parallel aree.

  • master Le direttive non sono consentite nell'estensione dinamica di for, sectionse single se le master direttive si associano alle direttive parallel di condivisione del lavoro.

  • ordered le direttive non sono consentite nell'estensione dinamica delle critical aree se le direttive si associano alle stesse parallel 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.