Condividi tramite


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-1a , 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_te 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 () {}