OpenMP OpenMP : API (Application Program Interface) utilizat pentru a controla explicit paralelismul cu memorie partajata si fire multiple de executie. Componente: -directive compilator; -functii runtime de biblioteca; -variabile de mediu. OpenMP este disponibil pentru C/C++ si Fortran. Exista implementari pe o multitudine de platforme, inclusiv Unix si Windows.
Model de programare OpenMP: memorie partajata si fire multiple de executie. -> model de programare explicit (nu automat) => programatorul are un control deplin asupra paralelismului. -> modelul fork-join de executie paralela:
Program OpenMP incepe cu un proces singular (thread master) -> secvential -> constructie de regiune paralela (FORK) -> mai multe thread-uri in paralel -> JOIN -> thread-ul master, etc. Numarul de thread-uri : se poate modifica dinamic. Structura unui program OpenMP : #include <opm.h> void main ( ) { int var1, var2, var3; ..... cod secvential ..... //incepe sectiunea paralela => FORK #pragma omp parallel private (var1, var2) shared (var3) ..... sectiune paralela executata de toate thread-urile ..... //toate thread-urile => JOIN => thread master } ..... reia cod secvential .....
Formatul unei directive #pragma omp nume_directiva [clauza, clauza, ...] newline unde clauzele pot fi plasate in orice ordine si se pot chiar repeta. Liniile directive lungi se pot continua pe randul urmator cu ’\’.
Directiva pentru regiune paralela Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. (constructia paralela OpenMP fundamentala !)
La o directiva parallel un thread creaza un set de thread-uri si devine masterul setului (cu numarul de thread 0 in cadrul setului). Numarul de thread-uri : omp_set_num_threads() sau cu variabila de mediu OMP_NUM_THREADS. Clauza if evalueaza expresia scalara: daca expresia ≠ 0 (adevarat) se creaza setul de thread-uri, iar daca expresia = 0 (fals) regiunea este executata numai de thread-ul master. La sfarsitul sectiunii paralele numai thread-ul master isi continua executia.
Exemplu: toate thread-urile executa codul corespunzator sectiunii paralele.
Directiva pentru partajarea lucrului Aceasta directiva imparte regiunea de cod intre thread-urile membre, fara insa sa lanseze noi thread-uri. Constructiile de partajare a lucrului sunt: -for; -sections; -single. O constructie de partajare a lucrului trebuie inclusa dinamic intr-o regiune paralela pentru a fi executata in paralel.
Directiva for Partajeaza iteratiile unei bucle for intre thread-urile setului (reprezinta un tip de paralelism de date):
Forma generala a directivei for este:
Clauza schedule, in functie de tip, descrie cum se impart iteratiile buclei for intre thread-urile din set: -static: iteratiile se impart in sectiuni de dimensiune chunk si asignate static thread-urilor (daca nu se specifica chunk, iteratiile se impart in mod egal). -dynamic: iteratiile se impart in sectiuni de dimensiune chunk si se asigneaza dinamic thread-urilor. Cand un thread termina bucata sa, acesta este asignat dinamic la o alta bucata din totalul de iteratii (valoarea implicita chunk=1). -guided: dimensiunea bucatii este redusa exponential cu fiecare bucata repartizata din totalul iteratiilor. Dimensiunea bucatii reprezinta numarul minim de iteratii de repartizat de fiecare data (implicit chunk=1). -runtime: decizia de repartizare este amanata pana in timpul executiei, fiind determinata de variabila de mediu OMP_SCHEDULE (nu se specifica dimensiune chunk). Clauza ordered trebuie sa fie prezenta cand sunt incluse in directiva for si directive ordered. Clauza nowait indica faptul ca thread-ul nu se sincronizeaza la sfarsitul buclei paralele.
Exemplu: program pentru adunarea a doi vectori.
Directiva sections Imparte lucrul in sectiuni discrete separate, fiecare sectiune fiind executata de un thread, pentru implementarea unui paralelism functional.
Forma generala a directivei sections este: Fiecare sectiune „section” se executa o singura data de catre un singur thread, sectiuni diferite vor fi executate de thread-uri diferite. Exista bariera implicita la sfarsit, numai daca nu se utilizeaza „nowait”.
Exemplu: adunarea a doi vectori, primele n/2 iteratii fiind distribuite primului thread, iar restul la un al doilea thread.
Directiva single Aceasta directiva serializeaza o sectiune de cod. Codul este executat de un singur thread. Forma generala:
Directiva parallel for Se pot combina directivele de sectiune paralela si de partajarea a lucrului cu ajutorul directivei parallel for, prin care se specifica o regiune paralela care contine o singura directiva for. Forma generala:
Exemplu: iteratiile sunt repartizate in blocuri de dimensiuni egale la toate thread-urile din set.
Directiva parallel sections Aceasta directiva specifica o regiune paralela continand o singura directiva sections. Forma generala este:
Directive de sincronizare Directiva master Specifica o regiune care este executata numai de thread-ul master, toate celelalte thread-uri ale setului o neglijeaza. Forma generala a directivei este:
Directiva critical Specifica o regiune de cod care trebuie executata numai de un singur thread la un moment dat.
Exemplu: mai multe thread-uri incrementeaza o locatie x, reprezentand o sectiune critica.
Directiva barrier Sincronizeaza toate thread-urile din set. Forma generala: #pragma omp barrier newline Aceasta directiva trebuie sa fie intalnita de catre toate thread-urile din set, sau de niciunul. Un thread care atinge bariera va astepta pana ce toate celelalte thread-uri ating bariera, dupa care se continua executia paralela. Exemplu: (obligatoriu aceasta directiva trebuie sa fie continuta intr-un bloc structurat).
Directiva atomic Indica actualizarea atomica (operatie indivizibila) a unei locatii de memorie, furnizand astfel o mini sectiune critica. Forma generala: Se aplica numai instructiunii imediat urmatoare, de forma: x binop = expr x++ ++x x-- --x
Alte directive Directiva flush: specifica un punct de sincronizare in care se furnizeaza o imagine consistenta a memoriei (toate variabilele vizibile thread-urilor se inscriu in memorie in acest punct). Directiva ordered: specifica faptul ca iteratiile buclei incluse in aceasta directiva vor fi executate in aceeasi ordine ca la executia pe un uniprocesor. Directiva threadprivate: face ca variabilele globale sa fie locale persistente unui thread in cadrul executiei regiunilor paralele multiple.
Clauze Clauza private declara variabilele din lista ca fiind private fiecarui thread: private (lista) Clauza shared declara variabilele din lista ca fiind partajate intre toate thread-urile: shared (lista) Clauza default permite utilizatorului sa specifice domeniul implicit al variabilelor din extinderea lexicala a unei regiuni paralele: default (shared | none)
Clauza firstprivate combina clauza private cu initializarea automata a variabilelor din lista: firstprivate (lista) Se initializeaza cu valorile obiectelor de origine inainte de intrarea in constructia paralela. Clauza lastprivate combina private cu o copie dupa ultima iteratie sau sectiune a obiectului variabila de origine: lastprivate (lista)
Clauza copyin asigneaza aceeasi valoare variabilelor threadprivate pentru toate thread-urile setului: copyin (lista) Sursa este setul de variabile ale thread-ului master. Clauza reduction executa o reducere asupra variabilelor din lista: reduction (operator : lista)
Exemplu: produs scalar a doi vectori.
-functii legate de numarul de thread-uri; OpenMP defineste un API pentru apeluri de functii din biblioteca, care executa o varietate de operatii: -functii legate de numarul de thread-uri; -functii de zavorare (semafoare); -functii de stabilire a mediului de executie. Variabila de zavorare trebuie sa fie de tipul omp_lock_t sau omp_nest_lock_t, depinzand de functia utilizata. void omp_set_num_threads(int num_threads) seteaza numarul de thread-uri care se vor utiliza in urmatoarea regiune paralela. In modul dinamic activat specifica numarul maxim de thread-uri, iar in modul dinamic dezactivat specifica numarul exact de thread-uri.
int omp_get_num_threads(void) returneaza numarul de thread-uri din set in regiunea paralela. int omp_get_max_threads(void) returneaza valoarea maxima care poate fi furnizata de un apel al functiei omp_get_num_threads. int omp_get_thread_num(void) returneaza identificatorul de thread in cadrul setului, fiind o valoare cuprinsa intre 0 si omp_get_num_threads – 1. Masterul primeste identificatorul 0.
int omp_get_num_procs(void) returneaza numarul de procesoare disponibile programului. int omp_in_parallel(void) returneaza un intreg ≠ 0 (adevarat) daca sectiunea de cod care se executa este paralela, respectiv un intreg = 0 (fals) daca sectiunea nu este paralela.
Aplicatie: calcularea valorii aproximative a numarului π. 1) Solutia secventiala.
2) Solutia cu thread-uri Win32. Caracteristici: -gestiunea thread-urilor si interactiunea intre thread-uri sunt explicite; -programatorul are control complet asupra thread-urilor. => se dubleaza dimensiunea codului.
3a) Solutia OpenMP - regiune paralela 3a) Solutia OpenMP - regiune paralela. Program SPMD: toate threa-urile executa acelasi cod, dar utilizand ID-ul thread-ului se poate specifica o executie selectiva.
3b) Solutia OpenMP – constructie de partajare a lucrului.
3c) Solutia OpenMP – clauza Private si o sectiune critica.
3d) Solutia OpenMP – parallel for cu o reducere. => OpenMP adauga un numar mic de linii de cod la solutia secventiala (2-4 linii)!