A. Příklady
Následují příklady konstruktorů definovaných v tomto dokumentu. Příkaz za direktivou je složen pouze v případě potřeby a nekompilovaný příkaz je odsazený z předchozí direktivy.
A.1 Jednoduchá smyčka paralelně
Následující příklad ukazuje, jak paralelizovat smyčku pomocí paralelizace direktivy. Proměnná iterace smyčky je ve výchozím nastavení soukromá, takže ji není nutné explicitně zadávat v privátní klauzuli.
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
Podmíněná kompilace A.2
Následující příklady ilustrují použití podmíněné kompilace pomocí makra OpenMP _OPENMP. Při kompilaci _OPENMP
OpenMP se makro definuje.
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
Definovaný operátor preprocesoru umožňuje testovat více než jedno makro v jedné direktivě.
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A.3 Paralelní oblasti
Paralelní direktivu lze použít v hrubých paralelních programech. V následujícím příkladu se každé vlákno v paralelní oblasti rozhodne, na jakou část globálního pole x
má pracovat, na základě čísla vlákna:
#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 Klauzule nowait
Pokud existuje mnoho nezávislých smyček v rámci paralelní oblasti, můžete použít klauzuli nowait , abyste se vyhnuli implicitní bariérě na konci for
direktivy, jak je znázorněno níže:
#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 Kritická direktiva
Následující příklad obsahuje několik kritických direktiv. Příklad znázorňuje model řazení do fronty, ve kterém je úkol vyřazen z fronty a pracuje na tom. Pokud chcete chránit před mnoha vlákny, které odřadí stejnou úlohu, musí být operace vyřazení z fronty v oddílu critical
. Vzhledem k tomu, že dvě fronty v tomto příkladu jsou nezávislé, jsou chráněny direktivami critical
s různými názvy, xaxis a 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 Klauzule lastprivate
Správné spuštění někdy závisí na hodnotě, kterou poslední iterace smyčky přiřadí proměnné. Tyto programy musí vypsat všechny takové proměnné jako argumenty do klauzule lastprivate , aby hodnoty proměnných byly stejné jako při postupném spuštění smyčky.
#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];
V předchozím příkladu se hodnota i
na konci paralelní oblasti rovná n-1
, jako v sekvenčním případě.
A.7 Klauzule redukce
Následující příklad ukazuje klauzuli redukce :
#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];
}
Paralelní oddíly A.8
V následujícím příkladu (pro oddíl 2.4.2) je možné souběžně spouštět funkce xaxis, yaxis a zaxis . První section
direktiva je nepovinná. Všechny section
direktivy musí být uvedeny v lexikálním rozsahu parallel sections
konstrukce.
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
Direktivy A.9 Single
Následující příklad ukazuje jedinou direktivu. V tomto příkladu vytiskne zprávu o průběhu pouze jedno vlákno (obvykle první vlákno, které narazí na direktivu single
). Uživatel nesmí provádět žádné předpoklady o tom, které vlákno provede single
oddíl. Všechna ostatní vlákna přeskočí single
oddíl a zastaví se u bariéry na konci single
konstrukce. Pokud mohou pokračovat další vlákna bez čekání na vlákno, které single
spouští oddíl, nowait
lze v direktivě single
zadat klauzuli.
#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();
}
A.10 Sekvenční řazení
Seřazené oddíly jsou užitečné pro postupné řazení výstupu z práce, která se provádí paralelně. Následující program vypíše indexy v sekvenčním pořadí:
#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 Pevný počet vláken
Některé programy spoléhají na pevný, předem zadaný počet vláken, aby se správně spustily. Vzhledem k tomu, že výchozí nastavení dynamické úpravy počtu vláken je definováno implementací, mohou se takové programy rozhodnout vypnout schopnost dynamických vláken a nastavit počet vláken explicitně pro zachování přenositelnosti. Následující příklad ukazuje, jak to provést pomocí omp_set_dynamic a 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);
}
V tomto příkladu program provede správně pouze v případě, že je spuštěn 16 vláken. Pokud implementace nepodporuje 16 vláken, chování tohoto příkladu je definované implementací.
Počet vláken, která spouští paralelní oblast, zůstává konstantní během paralelní oblasti bez ohledu na nastavení dynamických vláken. Mechanismus dynamických vláken určuje počet vláken, která se mají použít na začátku paralelní oblasti, a udržuje ji konstantní po dobu trvání oblasti.
A.12 Atomická direktiva
Následující příklad zabraňuje časování podmínek (souběžné aktualizace prvku x více vláken) pomocí atomické direktivy:
#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);
}
Výhodou použití direktivy atomic
v tomto příkladu je, že umožňuje paralelně provádět aktualizace dvou různých prvků x. Pokud se místo toho použije kritická direktiva, všechny aktualizace prvků x se spustí sériově (i když ne v žádném zaručené pořadí).
Direktiva atomic
se vztahuje pouze na příkaz C nebo C++ bezprostředně za ním. V tomto příkladu se proto prvky y neaktualizují atomicky.
A.13 Direktiva flush se seznamem
Následující příklad používá direktivu flush
pro synchronizaci konkrétních objektů mezi páry vláken:
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 Direktiva flush bez seznamu
Následující příklad (pro oddíl 2.6.5) rozlišuje sdílené objekty ovlivněné direktivou flush
bez seznamu ze sdílených objektů, které nejsou ovlivněny:
// 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 Počet použitých vláken
Představte si následující nesprávný příklad (v oddílu 3.1.2):
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
Volání omp_get_num_threads()
vrátí hodnotu 1 v sériové části kódu, takže np bude vždy rovna 1 v předchozím příkladu. K určení počtu vláken, která budou nasazena pro paralelní oblast, by volání mělo být uvnitř paralelní oblasti.
Následující příklad ukazuje, jak přepsat tento program bez zahrnutí dotazu na počet vláken:
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
Zámky A.16
V následujícím příkladu (pro oddíl 3.2) by měl argument funkcí zámku obsahovat typ omp_lock_t
a že není nutné ho vyprázdnit. Funkce uzamčení způsobí, že vlákna budou nečinná při čekání na vstup do první kritické části, ale při čekání na vstup na druhý provede další práci. Funkce omp_set_lock
blokuje, ale omp_test_lock
funkce neumožňuje provádět práci 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 Vnořené zámky
Následující příklad (pro oddíl 3.2) ukazuje, jak lze použít vnořený zámek k synchronizaci aktualizací celé struktury i jednoho ze svých členů.
#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 Vnořené direktivy
Následující příklad vnoření for
direktiv je kompatibilní, protože vnitřní a vnější for
direktivy se sváže s různými paralelními oblastmi:
#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);
}
}
}
Následující varianta předchozího příkladu je také kompatibilní:
#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 Příklady ukazující nesprávné vnoření direktiv sdílení práce
Příklady v této části ilustrují pravidla vnoření direktiv.
Následující příklad nedodržuje předpisy, protože vnitřní a vnější for
direktivy jsou vnořené a svázané se stejnou parallel
direktivou:
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);
}
}
}
Následující dynamicky vnořená verze předchozího příkladu také nedodržuje předpisy:
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);
}
Následující příklad nedodržuje předpisy, protože direktivy for
a single
direktivy jsou vnořené a jsou svázány se stejnou paralelní oblastí:
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);
}
}
}
Následující příklad nedodržuje předpisy, protože direktiva barrier
uvnitř for
můžou vést k vzájemnému zablokování:
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);
}
}
}
Následující příklad nedodržuje předpisy, protože barrier
výsledkem je zablokování z důvodu skutečnosti, že v jednu chvíli může do kritické části vstoupit pouze jedno vlákno:
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
Následující příklad nedodržuje single
předpisy, protože barrier
výsledkem je zablokování kvůli skutečnosti, že oddíl spouští pouze jedno vlákno:
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
A.20 Direktivy bind bariéry
Pravidla vazby direktiv volají direktivu barrier
, která se má svázat s nejbližší uzavřenou direktivou parallel
. Další informace o vazbě direktiv najdete v části 2.8.
V následujícím příkladu je volání z hlavní k pod2 kompatibilní, protože barrier
(v pod3) je vázána na paralelní oblast v sub2. Volání z hlavního na pod1 je kompatibilní, protože barrier
vazby k paralelní oblasti v podprogramu sub2. Volání z hlavní k pod3 je kompatibilní, protože barrier
se neváže na žádnou paralelní oblast a ignoruje se. Kromě toho barrier
synchronizuje pouze tým vláken v uzavřené paralelní oblasti a ne všechny vlákna vytvořená v pod1.
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 Proměnné oboru s privátní klauzulí
Hodnoty i
a j
v následujícím příkladu nejsou definovány při ukončení z paralelní oblasti:
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);
Další informace o klauzuli najdete v oddílu private
2.7.2.1.
A.22 Klauzule default(none)
Následující příklad rozlišuje proměnné, které jsou ovlivněny default(none)
klauzulí, od proměnných, které nejsou:
// 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
}
}
Další informace o klauzuli najdete v oddílu default
2.7.2.5.
A.23 Příklady seřazené direktivy
Je možné mít mnoho uspořádaných oddílů se for
zadaným klauzulí ordered
. První příklad nedodržuje předpisy, protože rozhraní API určuje následující pravidlo:
"Iterace smyčky s konstruktorem for
nesmí spustit stejnou ordered
direktivu více než jednou a nesmí spustit více než jednu ordered
direktivu." (Viz bod 2.6.6.)
V tomto příkladu nedodržující předpisy všechny iterace provádějí dvě uspořádané části:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
Následující příklad vyhovující předpisům for
ukazuje více než jeden seřazený oddíl:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
A.24 Příklad privátní klauzule
Soukromá klauzule paralelní oblasti je platná pouze pro lexikální rozsah oblasti, nikoli pro dynamický rozsah oblasti. V následujícím příkladu tedy jakékoli použití proměnné uvnitř smyčky v rutině f odkazuje na soukromou kopii a zatímco použití v rutině g odkazuje na globální hodnotu a.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 Příklady klauzule atributu data copyprivate
Příklad 1: Klauzule copyprivate lze použít k vysílání hodnot získaných jedním vláknem přímo do všech instancí privátních proměnných v ostatních vláknech.
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);
}
Pokud je rutinní init volána ze sériové oblasti, jeho chování není ovlivněno přítomností direktiv. Po volání rutiny get_values byla provedena jedním vláknem, žádné vlákno neopustí konstruktor, dokud soukromé objekty určené a, b, x a y ve všech vláknech byly definovány s hodnotami přečtenými.
Příklad 2: Na rozdíl od předchozího příkladu předpokládejme, že čtení musí provést konkrétní vlákno, řekněme hlavní vlákno. V tomto případě copyprivate
se klauzule nedá použít k přímému vysílání, ale dá se použít k poskytnutí přístupu k dočasnému sdílenému objektu.
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;
}
Příklad 3: Předpokládejme, že počet objektů zámků požadovaných v rámci paralelní oblasti nelze snadno určit před jeho zadáním. Klauzuli copyprivate
lze použít k poskytnutí přístupu ke sdíleným objektům uzamčení, které jsou přiděleny v rámci této paralelní oblasti.
#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 Direktiva threadprivate
Následující příklady ukazují, jak pomocí direktivy threadprivate dát každému vláknu samostatný čítač.
Příklad 1
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
Příklad 2
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
Pole s proměnnou délkou A.27 C99
Následující příklad ukazuje použití C99 Variable Length Arrays (VLAs) v direktivě firstprivate .
Poznámka:
Pole délky proměnných nejsou v současné době podporována v jazyce Visual C++.
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 Klauzule num_threads
Následující příklad ukazuje klauzuli num_threads . Paralelní oblast se provádí s maximálně 10 vlákny.
#include <omp.h>
main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
A.29 Konstrukce sdílení práce uvnitř kritické konstrukce
Následující příklad ukazuje použití konstruktoru sdílení práce uvnitř konstruktoru critical
. Tento příklad je kompatibilní, protože konstruktor sdílení práce a critical
konstruktor není vázán na stejnou paralelní oblast.
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 Reprivatization
Následující příklad ukazuje reprivatizaci proměnných. Privátní proměnné lze znovu označit private
ve vnořené direktivě. Tyto proměnné nemusíte sdílet v uzavřené paralelní oblasti.
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
A.31 Funkce zámku bezpečného pro přístup z více vláken
Následující příklad jazyka C++ ukazuje, jak inicializovat pole zámků v paralelní oblasti pomocí 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 () {}