R. Esempi
Di seguito sono riportati alcuni esempi dei costrutti definiti in questo documento. Un'istruzione che segue una direttiva è composta solo quando necessario e un'istruzione non composta viene rientrata da una direttiva precedente.
A.1 Ciclo semplice in parallelo
Nell'esempio seguente viene illustrato come parallelizzare un ciclo usando la direttiva for parallela . La variabile di iterazione del ciclo è privata per impostazione predefinita, pertanto non è necessario specificarla in modo esplicito in una clausola privata.
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
A.2 Compilazione condizionale
Negli esempi seguenti viene illustrato l'uso della compilazione condizionale tramite la macro OpenMP _OPENMP. Con la compilazione OpenMP, la _OPENMP
macro viene definita.
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
L'operatore del preprocessore definito consente di testare più di una macro in una singola direttiva.
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A.3 Aree parallele
La direttiva parallela può essere usata in programmi paralleli con granularità grossolana. Nell'esempio seguente ogni thread nell'area parallela decide quale parte della matrice x
globale usare, in base al numero di thread:
#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
iam = omp_get_thread_num();
np = omp_get_num_threads();
ipoints = npoints / np;
subdomain(x, iam, ipoints);
}
A.4 Clausola nowait
Se sono presenti molti cicli indipendenti all'interno di un'area parallela, è possibile usare la clausola nowait per evitare la barriera implicita alla fine della for
direttiva, come indicato di seguito:
#pragma omp parallel
{
#pragma omp for nowait
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
#pragma omp for nowait
for (i=0; i<m; i++)
y[i] = sqrt(z[i]);
}
A.5 Direttiva critica
L'esempio seguente include diverse direttive critiche . L'esempio illustra un modello di accodamento in cui un'attività viene dequeued e ha lavorato. Per evitare che molti thread dequeuno la stessa attività, l'operazione di dequeuing deve trovarsi in una critical
sezione. Poiché le due code in questo esempio sono indipendenti, sono protette da critical
direttive con nomi diversi, xaxis e yaxis.
#pragma omp parallel shared(x, y) private(x_next, y_next)
{
#pragma omp critical ( xaxis )
x_next = dequeue(x);
work(x_next);
#pragma omp critical ( yaxis )
y_next = dequeue(y);
work(y_next);
}
A.6 Clausola lastprivate
L'esecuzione corretta a volte dipende dal valore assegnato dall'ultima iterazione di un ciclo a una variabile. Tali programmi devono elencare tutte le variabili come gli argomenti di una clausola lastprivate in modo che i valori delle variabili siano uguali a quelli in cui il ciclo viene eseguito in sequenza.
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];
Nell'esempio precedente, il valore di i
alla fine dell'area parallela sarà uguale n-1
a , come nel caso sequenziale.
A.7 Clausola di riduzione
Nell'esempio seguente viene illustrata la clausola di riduzione :
#pragma omp parallel for private(i) shared(x, y, n) \
reduction(+: a, b)
for (i=0; i<n; i++) {
a = a + x[i];
b = b + y[i];
}
Sezioni parallele A.8
Nell'esempio seguente (per la sezione 2.4.2), le funzioni xaxis, yaxis e zaxis possono essere eseguite contemporaneamente. La prima section
direttiva è facoltativa. Tutte le section
direttive devono essere visualizzate nell'estensione lessicale del parallel sections
costrutto.
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
Direttive singole A.9
Nell'esempio seguente viene illustrata la singola direttiva . Nell'esempio, un solo thread (in genere il primo thread che incontra la single
direttiva ) stampa il messaggio di stato. L'utente non deve fare ipotesi relative al thread che eseguirà la single
sezione. Tutti gli altri thread salteranno la single
sezione e si fermeranno alla barriera alla fine del single
costrutto. Se altri thread possono continuare senza attendere l'esecuzione del thread della single
sezione, è possibile specificare una nowait
clausola nella single
direttiva .
#pragma omp parallel
{
#pragma omp single
printf_s("Beginning work1.\n");
work1();
#pragma omp single
printf_s("Finishing work1.\n");
#pragma omp single nowait
printf_s("Finished work1 and beginning work2.\n");
work2();
}
Ordinamento sequenziale A.10
Le sezioni ordinate sono utili per ordinare in sequenza l'output dal lavoro eseguito in parallelo. Il programma seguente stampa gli indici in ordine sequenziale:
#pragma omp for ordered schedule(dynamic)
for (i=lb; i<ub; i+=st)
work(i);
void work(int k)
{
#pragma omp ordered
printf_s(" %d", k);
}
A.11 Numero fisso di thread
Alcuni programmi si basano su un numero fisso e prespecificato di thread per l'esecuzione corretta. Poiché l'impostazione predefinita per la regolazione dinamica del numero di thread è definita dall'implementazione, tali programmi possono scegliere di disattivare la funzionalità thread dinamici e impostare il numero di thread in modo esplicito per mantenere la portabilità. L'esempio seguente illustra come eseguire questa operazione usando omp_set_dynamic e omp_set_num_threads:
omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
if (omp_get_num_threads() != 16)
abort();
iam = omp_get_thread_num();
ipoints = npoints/16;
do_by_16(x, iam, ipoints);
}
In questo esempio il programma viene eseguito correttamente solo se viene eseguito da 16 thread. Se l'implementazione non è in grado di supportare 16 thread, il comportamento di questo esempio è definito dall'implementazione.
Il numero di thread che eseguono un'area parallela rimane costante durante un'area parallela, indipendentemente dall'impostazione dei thread dinamici. Il meccanismo dei thread dinamici determina il numero di thread da usare all'inizio dell'area parallela e lo mantiene costante per la durata dell'area.
A.12 Direttiva atomica
L'esempio seguente evita le race condition (aggiornamenti simultanei di un elemento x per molti thread) usando la direttiva atomica:
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++)
{
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
Il vantaggio dell'uso della atomic
direttiva in questo esempio è che consente gli aggiornamenti di due diversi elementi di x in parallelo. Se invece viene usata una direttiva critica , tutti gli aggiornamenti agli elementi di x vengono eseguiti in modo seriale (anche se non in un ordine garantito).
La atomic
direttiva si applica solo all'istruzione C o C++ immediatamente successiva. Di conseguenza, gli elementi di y non vengono aggiornati in modo atomico in questo esempio.
A.13 Direttiva flush con un elenco
Nell'esempio seguente viene usata la direttiva per la flush
sincronizzazione da punto a punto di oggetti specifici tra coppie di thread:
int sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
iam = omp_get_thread_num();
sync[iam] = 0;
#pragma omp barrier
// Do computation into my portion of work array
work[iam] = ...;
// Announce that I am done with my work
// The first flush ensures that my work is
// made visible before sync.
// The second flush ensures that sync is made visible.
#pragma omp flush(work)
sync[iam] = 1;
#pragma omp flush(sync)
// Wait for neighbor
neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
while (sync[neighbor]==0)
{
#pragma omp flush(sync)
}
// Read neighbor's values of work array
... = work[neighbor];
}
A.14 Direttiva flush senza un elenco
L'esempio seguente (per la sezione 2.6.5) distingue gli oggetti condivisi interessati da una flush
direttiva senza alcun elenco degli oggetti condivisi che non sono interessati:
// omp_flush_without_list.c
#include <omp.h>
int x, *p = &x;
void f1(int *q)
{
*q = 1;
#pragma omp flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
void f2(int *q)
{
#pragma omp barrier
*q = 2;
#pragma omp barrier
// a barrier implies a flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
int g(int n)
{
int i = 1, j, sum = 0;
*p = 1;
#pragma omp parallel reduction(+: sum) num_threads(10)
{
f1(&j);
// i, n and sum were not flushed
// because they were not accessible in f1
// j was flushed because it was accessible
sum += j;
f2(&j);
// i, n, and sum were not flushed
// because they were not accessible in f2
// j was flushed because it was accessible
sum += i + j + *p + n;
}
return sum;
}
int main()
{
}
A.15 Numero di thread usati
Si consideri l'esempio errato seguente (per la sezione 3.1.2):
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
La omp_get_num_threads()
chiamata restituisce 1 nella sezione seriale del codice, pertanto np sarà sempre uguale a 1 nell'esempio precedente. Per determinare il numero di thread che verranno distribuiti per l'area parallela, la chiamata deve trovarsi all'interno dell'area parallela.
L'esempio seguente illustra come riscrivere questo programma senza includere una query per il numero di thread:
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
Blocchi A.16
Nell'esempio seguente (per la sezione 3.2), l'argomento delle funzioni di blocco deve avere tipo omp_lock_t
e che non è necessario scaricarlo. Le funzioni di blocco causano l'inattività dei thread durante l'attesa dell'immissione nella prima sezione critica, ma per eseguire altre operazioni durante l'attesa dell'ingresso al secondo. La omp_set_lock
funzione si blocca, ma la omp_test_lock
funzione non consente di eseguire il lavoro.skip()
// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>
void work(int);
void skip(int);
int main() {
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
printf_s("My thread id is %d.\n", id);
// only one thread at a time can execute this printf
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); // we do not yet have the lock,
// so we must do something else
}
work(id); // we now have the lock
// and can do the work
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
}
A.17 Blocchi annidabili
Nell'esempio seguente (per la sezione 3.2) viene illustrato come usare un blocco annidabile per sincronizzare gli aggiornamenti sia in una struttura intera che in uno dei relativi membri.
#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;
void incr_a(pair *p, int a)
{
// Called only from incr_pair, no need to lock.
p->a += a;
}
void incr_b(pair *p, int b)
{
// Called both from incr_pair and elsewhere,
// so need a nestable lock.
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void f(pair *p)
{
extern int work1(), work2(), work3();
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
A.18 Annidato per le direttive
L'esempio seguente di annidamento delle for
direttive è conforme perché le direttive interne ed esterne for
sono associate a aree parallele diverse:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
{
#pragma omp parallel shared(i, n)
{
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
È inoltre conforme una variante seguente dell'esempio precedente:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
void work1(int i, int n)
{
int j;
#pragma omp parallel default(shared)
{
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
return;
}
A.19 Esempi che mostrano un annidamento errato delle direttive di condivisione del lavoro
Gli esempi in questa sezione illustrano le regole di annidamento delle direttive.
L'esempio seguente non è conforme perché le direttive interne ed esterne for
sono annidate e si associano alla stessa parallel
direttiva:
void wrong1(int n)
{
#pragma omp parallel default(shared)
{
int i, j;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Anche la seguente versione annidata dinamica dell'esempio precedente non è conforme:
void wrong2(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
}
void work1(int i, int n)
{
int j;
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
L'esempio seguente non è conforme perché le for
direttive e single
sono annidate e si associano alla stessa area parallela:
void wrong3(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp single
work(i);
}
}
}
L'esempio seguente non è conforme perché una barrier
direttiva all'interno di un for
può comportare un deadlock:
void wrong4(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
work1(i);
#pragma omp barrier
work2(i);
}
}
}
L'esempio seguente non è conforme perché genera barrier
un deadlock a causa del fatto che solo un thread alla volta può immettere la sezione critica:
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
L'esempio seguente non è conforme perché genera barrier
un deadlock a causa del fatto che viene eseguita la single
sezione solo un thread:
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
Direttive delle barriere bind A.20
Le regole di associazione di direttiva richiedono che una barrier
direttiva venga associata alla direttiva di inclusione parallel
più vicina. Per altre informazioni sull'associazione di direttiva, vedere la sezione 2.8.
Nell'esempio seguente la chiamata da main a sub2 è conforme perché barrier
(in sub3) viene associata all'area parallela in sub2. La chiamata da main a sub1 è conforme perché associa all'area barrier
parallela nella subroutine subroutine sub2. La chiamata da main a sub3 è conforme perché non esegue l'associazione barrier
ad alcuna area parallela e viene ignorata. Inoltre, l'unico barrier
sincronizza il team di thread nell'area parallela di inclusione e non tutti i thread creati in sub1.
int main()
{
sub1(2);
sub2(2);
sub3(2);
}
void sub1(int n)
{
int i;
#pragma omp parallel private(i) shared(n)
{
#pragma omp for
for (i=0; i<n; i++)
sub2(i);
}
}
void sub2(int k)
{
#pragma omp parallel shared(k)
sub3(k);
}
void sub3(int n)
{
work(n);
#pragma omp barrier
work(n);
}
A.21 Variabili di ambito con la clausola privata
I valori di i
e j
nell'esempio seguente non sono definiti all'uscita dall'area parallela:
int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
i = 3;
j = j + 2;
}
printf_s("%d %d\n", i, j);
Per altre informazioni sulla private
clausola , vedere la sezione 2.7.2.1.
A.22 Clausola default(none)
L'esempio seguente distingue le variabili interessate dalla default(none)
clausola dalle variabili che non sono:
// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>
int x, y, z[1000];
#pragma omp threadprivate(x)
void fun(int a) {
const int c = 1;
int i = 0;
#pragma omp parallel default(none) private(a) shared(z)
{
int j = omp_get_num_thread();
//O.K. - j is declared within parallel region
a = z[j]; // O.K. - a is listed in private clause
// - z is listed in shared clause
x = c; // O.K. - x is threadprivate
// - c has const-qualified type
z[i] = y; // C3052 error - cannot reference i or y here
#pragma omp for firstprivate(y)
for (i=0; i<10 ; i++) {
z[i] = y; // O.K. - i is the loop control variable
// - y is listed in firstprivate clause
}
z[i] = y; // Error - cannot reference i or y here
}
}
Per altre informazioni sulla default
clausola , vedere la sezione 2.7.2.5.
A.23 Esempi della direttiva ordinata
È possibile avere molte sezioni ordinate con un for
oggetto specificato con la ordered
clausola . Il primo esempio non è conforme perché l'API specifica la regola seguente:
"Un'iterazione di un ciclo con un for
costrutto non deve eseguire la stessa ordered
direttiva più di una volta e non deve eseguire più di una ordered
direttiva". Vedere la sezione 2.6.6.
In questo esempio non conforme tutte le iterazioni eseguono due sezioni ordinate:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
L'esempio conforme seguente mostra un for
oggetto con più sezioni ordinate:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
A.24 Esempio della clausola privata
La clausola privata di un'area parallela è attiva solo per l'estensione lessicale dell'area, non per l'estensione dinamica dell'area. Nell'esempio seguente, pertanto, qualsiasi utilizzo della variabile all'interno del ciclo nella routine f fa riferimento a una copia privata di un oggetto, mentre un utilizzo nella routine g fa riferimento a un oggetto globale.for
int a;
void f(int n)
{
a = 0;
#pragma omp parallel for private(a)
for (int i=1; i<n; i++)
{
a = i;
g(i, n);
d(a); // Private copy of "a"
...
}
...
void g(int k, int n)
{
h(k,a); // The global "a", not the private "a" in f
}
A.25 Esempi della clausola dell'attributo copyprivate data
Esempio 1: la clausola copyprivate può essere usata per trasmettere i valori acquisiti da un singolo thread direttamente a tutte le istanze delle variabili private negli altri thread.
float x, y;
#pragma omp threadprivate(x, y)
void init( )
{
float a;
float b;
#pragma omp single copyprivate(a,b,x,y)
{
get_values(a,b,x,y);
}
use_values(a, b, x, y);
}
Se l'init di routine viene chiamato da un'area seriale, il relativo comportamento non è influenzato dalla presenza delle direttive. Dopo che la chiamata alla routine get_values è stata eseguita da un thread, nessun thread lascia il costrutto fino a quando gli oggetti privati designati da a, b, x e y in tutti i thread sono diventati definiti con i valori letti.
Esempio 2: a differenza dell'esempio precedente, si supponga che la lettura debba essere eseguita da un thread specifico, ad esempio il thread master. In questo caso, la copyprivate
clausola non può essere usata per eseguire direttamente la trasmissione, ma può essere usata per fornire l'accesso a un oggetto condiviso temporaneo.
float read_next( )
{
float * tmp;
float return_val;
#pragma omp single copyprivate(tmp)
{
tmp = (float *) malloc(sizeof(float));
}
#pragma omp master
{
get_float( tmp );
}
#pragma omp barrier
return_val = *tmp;
#pragma omp barrier
#pragma omp single
{
free(tmp);
}
return return_val;
}
Esempio 3: si supponga che il numero di oggetti di blocco necessari all'interno di un'area parallela non possa essere determinato facilmente prima di immetterlo. La copyprivate
clausola può essere usata per fornire l'accesso agli oggetti di blocco condiviso allocati all'interno di tale area parallela.
#include <omp.h>
omp_lock_t *new_lock()
{
omp_lock_t *lock_ptr;
#pragma omp single copyprivate(lock_ptr)
{
lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
omp_init_lock( lock_ptr );
}
return lock_ptr;
}
A.26 Direttiva threadprivate
Negli esempi seguenti viene illustrato come usare la direttiva threadprivate per assegnare a ogni thread un contatore separato.
Esempio 1
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
Esempio 2
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
Matrici di lunghezza variabile A.27 C99
Nell'esempio seguente viene illustrato come usare matrici di lunghezza variabile C99 (VLA) in una direttiva firstprivate .
Nota
Le matrici a lunghezza variabile non sono attualmente supportate in Visual C++.
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 Clausola num_threads
Nell'esempio seguente viene illustrata la clausola num_threads . L'area parallela viene eseguita con un massimo di 10 thread.
#include <omp.h>
main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
A.29 Costrutti di condivisione di lavoro all'interno di un costrutto critico
Nell'esempio seguente viene illustrato l'uso di un costrutto di condivisione di lavoro all'interno di un critical
costrutto. Questo esempio è conforme perché il costrutto di condivisione di lavoro e il critical
costrutto non vengono associati alla stessa area parallela.
void f()
{
int i = 1;
#pragma omp parallel sections
{
#pragma omp section
{
#pragma omp critical (name)
{
#pragma omp parallel
{
#pragma omp single
{
i++;
}
}
}
}
}
}
A.30 Riprivatization
Nell'esempio seguente viene illustrata la riprivazione delle variabili. Le variabili private possono essere contrassegnate private
di nuovo in una direttiva annidata. Non è necessario condividere tali variabili nell'area parallela di inclusione.
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
A.31 Funzioni di blocco thread-safe
Nell'esempio C++ seguente viene illustrato come inizializzare una matrice di blocchi in un'area parallela usando omp_init_lock.
// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i = 0 ; i < 1000 ; i++)
omp_init_lock(&lock[i]);
return lock;
}
int main () {}