This shows you the differences between two versions of the page.
app:laboratoare:01 [2022/10/23 14:37] florin.mihalache [Sintaxa OpenMP] |
app:laboratoare:01 [2022/10/23 14:46] (current) florin.mihalache |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 1 - Introducere în OpenMP ===== | + | ====== Laboratorul 1 - Introducere în OpenMP ====== |
- | ==== Ce învățăm la APP? ==== | + | ===== Ce învățăm la APP? ===== |
La APP vom învăța cum să analizăm o problemă și o soluție a acesteia (mai precis, un program secvențial) și cum am putea să îmbunătățim soluțiile la problema respectivă (adică cum am putea să eficientizăm prin paralelizare soluțiile problemei). | La APP vom învăța cum să analizăm o problemă și o soluție a acesteia (mai precis, un program secvențial) și cum am putea să îmbunătățim soluțiile la problema respectivă (adică cum am putea să eficientizăm prin paralelizare soluțiile problemei). | ||
În cadrul laboratoarelor de APP vom studia despre programarea paralelă (OpenMP, pthreads), despre programarea distribuită (MPI) și despre analiza performanțelor unui program (profiling). | În cadrul laboratoarelor de APP vom studia despre programarea paralelă (OpenMP, pthreads), despre programarea distribuită (MPI) și despre analiza performanțelor unui program (profiling). | ||
- | ==== Ce este OpenMP? ==== | + | ===== Ce este OpenMP? ===== |
OpenMP reprezintă un API prin care putem paraleliza programe secvențiale scrise în C/C++. Acesta este un API high-level, în sensul că programatorul are o varietate de tool-uri și de opțiuni la dispoziția sa, ele putând fi folosite cu mare ușurință. | OpenMP reprezintă un API prin care putem paraleliza programe secvențiale scrise în C/C++. Acesta este un API high-level, în sensul că programatorul are o varietate de tool-uri și de opțiuni la dispoziția sa, ele putând fi folosite cu mare ușurință. | ||
Line 12: | Line 12: | ||
{{ :app:laboratoare:fork_join.png?700 |}} | {{ :app:laboratoare:fork_join.png?700 |}} | ||
- | ==== Includere și compilare ==== | + | ====== Includere și compilare ====== |
Pentru a putea folosi OpenMP în cod, trebuie inclusă biblioteca omp.h în cod: ''#include <omp.h>''. | Pentru a putea folosi OpenMP în cod, trebuie inclusă biblioteca omp.h în cod: ''#include <omp.h>''. | ||
Line 19: | Line 19: | ||
* SunStudio (pe fep): ''cc -xopenmp helloworld.c -o helloworld'' | * SunStudio (pe fep): ''cc -xopenmp helloworld.c -o helloworld'' | ||
- | ==== Sintaxa OpenMP ==== | + | ===== Sintaxa OpenMP ===== |
Pentru OpenMP, se folosesc directive de compilare de tip pragma pentru a marca blocuri de cod paralelizate și pentru a folosi elemente de sincronizare. | Pentru OpenMP, se folosesc directive de compilare de tip pragma pentru a marca blocuri de cod paralelizate și pentru a folosi elemente de sincronizare. | ||
Line 25: | Line 25: | ||
Exemplu: ''#pragma omp parallel default(shared) private(beta, pi)'' | Exemplu: ''#pragma omp parallel default(shared) private(beta, pi)'' | ||
- | === Paralelizarea secvențelor de cod === | + | ==== Paralelizarea secvențelor de cod ==== |
Pentru ca o bucată de cod să fie executată de mai multe thread-uri, folosim directiva ''#pragma omp parallel'' prin care marcăm faptul că acea zonă de cod este executată în paralel de mai multe thread-uri. | Pentru ca o bucată de cod să fie executată de mai multe thread-uri, folosim directiva ''#pragma omp parallel'' prin care marcăm faptul că acea zonă de cod este executată în paralel de mai multe thread-uri. | ||
Line 75: | Line 75: | ||
</code> | </code> | ||
- | ==== Funcții utile ==== | + | ===== Funcții utile ===== |
Pentru setarea numărului de thread-uri din cadrul programului paralelizat, putem să facem în două moduri: | Pentru setarea numărului de thread-uri din cadrul programului paralelizat, putem să facem în două moduri: | ||
* din linia de comandă: ''export OMP_NUM_THREADS=8'' | * din linia de comandă: ''export OMP_NUM_THREADS=8'' | ||
Line 96: | Line 96: | ||
* numărul de procesoare: ''omp_get_num_procs()'' | * numărul de procesoare: ''omp_get_num_procs()'' | ||
- | ==== Vizibilitatea variabilelor ==== | + | ===== Vizibilitatea variabilelor ===== |
Variabilele în cadrul blocurilor paralele pot fi: | Variabilele în cadrul blocurilor paralele pot fi: | ||
* globale - văzute și partajate de toate thread-urile | * globale - văzute și partajate de toate thread-urile | ||
Line 140: | Line 140: | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | ===== Paralelizarea buclelor ===== | ||
+ | În OpenMP putem paraleliza buclele de tip for folosind directiva ''#pragma omp for'' în cadrul unei zone paralele. În acest fel, iterațiile din for sunt împărțite egal thread-urilor, fiecare thread având iterațiile sale din cadrul buclei for. | ||
+ | |||
+ | Paralelizarea buclelor poate fi eficientizată folosind directiva ''SCHEDULE'', despre care vom discuta în laboratorul 2. | ||
+ | |||
+ | Exemplu de folosire: | ||
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <omp.h> | ||
+ | |||
+ | int main(int argc, char** argv) { | ||
+ | int i, x[20]; | ||
+ | #pragma omp parallel private(i) shared(x) | ||
+ | { | ||
+ | #pragma omp for | ||
+ | for (i = 0; i < 20; i++) { | ||
+ | x[i] = i; | ||
+ | printf("iteration no. %d | thread no. %d\n", i, omp_get_thread_num()); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | printf("\n"); | ||
+ | |||
+ | // o alta forma, aceeasi functionalitate | ||
+ | #pragma omp parallel for private(i) shared(x) | ||
+ | for (i = 0; i < 20; i++) { | ||
+ | x[i] = i; | ||
+ | printf("iteration no. %d | thread no. %d\n", i, omp_get_thread_num()); | ||
+ | } | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ===== Elemente de sincronizare ===== | ||
+ | În OpenMP avem la dispoziție elemente de sincronizare, prin care putem să ne asigurăm faptul că soluția paralelizată funcționează corect, fără probleme în ceea ce privește rezultatele incorecte sau deadlocks. | ||
+ | |||
+ | ==== Mutex ==== | ||
+ | Pentru zonele critice, unde avem operații de read-write, folosim directiva #pragma omp critical, care reprezintă un mutex, echivalentul lui ''pthread_mutex_t'' din pthreads, care asigură faptul că un singur thread accesează zona critică la un moment dat, thread-ul deținând lock-ul pe zona critică în momentul respectiv, și că celelalte thread-uri care nu au intrat încă în zona critică așteaptă eliberarea lock-ului de către thread-ul aflat în zona critică în acel moment. | ||
+ | |||
+ | Exemplu de folosire: | ||
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <omp.h> | ||
+ | |||
+ | int main (int argc, char** argv) { | ||
+ | int thread_id, sum = 0; | ||
+ | #pragma omp parallel private(thread_id) shared(sum) | ||
+ | { | ||
+ | thread_id = omp_get_thread_num(); | ||
+ | #pragma omp critical | ||
+ | sum += thread_id; | ||
+ | } | ||
+ | printf("%d",sum); | ||
+ | | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ==== Barieră ==== | ||
+ | Un alt element de sincronizare reprezintă bariera, care asigură faptul că niciun thread gestionat de barieră nu trece mai departe de aceasta decât atunci cand toate thread-urile gestionate de barieră au ajuns la punctul unde se află bariera. | ||
+ | |||
+ | În OpenMP, pentru barieră avem directiva ''#pragma omp barrier'', echivalent cu ''pthread_barrier_t'' din pthreads. | ||
+ | |||
+ | Exemplu de folosire: | ||
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <omp.h> | ||
+ | |||
+ | int main (int argc, char** argv) { | ||
+ | #pragma omp parallel | ||
+ | { | ||
+ | printf("First print by %d\n", omp_get_thread_num()); | ||
+ | #pragma omp barrier | ||
+ | printf("Second print by %d\n", omp_get_thread_num()); | ||
+ | } | ||
+ | | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ===== Reduction ===== | ||
+ | ''reduction'' este o directivă folosită pentru operații de tip reduce / fold pe arrays / colecții sau simple însumări / înmulțiri în cadrul unui loop. Mai precis, elementele dintr-un array sau indecșii unui loop sunt "acumulați" într-o singură variabilă, cu ajutorul unei operații, al cărui semn este precizat. | ||
+ | |||
+ | Tipar: ''reduction(operator_operatie:variabila_in_care_se_acumuleaza)'' | ||
+ | |||
+ | Exemplu de reduction: ''reduction(+:sum)'', unde se însumează elementele unui array în variabila ''sum'' | ||
+ | |||
+ | Exemplu de folosire de reduction: | ||
+ | <code c> | ||
+ | int sum = 0; | ||
+ | |||
+ | #pragma omp parallel for reduction(+:sum) private(i) | ||
+ | for (i = 1; i <= num_steps; i++) { | ||
+ | sum += i; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ===== Exerciții ===== | ||
+ | - Descărcați [[https://github.com/cs-pub-ro/app-labs/tree/master/lab1/skel | scheletul de laborator]] și [[https://github.com/cs-pub-ro/app-labs/tree/master/lab1/demo | demo-ul pentru laborator]]. Rulați exemplele din demo. | ||
+ | - Paralelizați însumarea elementelor unui array, încât suma să fie corectă, folosind fișierul ''array_sum.c'' din schelet, unde este implementată suma serială a elementelor dintr-un array. | ||
+ | |||
+ | ===== Resurse utile ===== | ||
+ | * http://jakascorner.com/blog/ | ||
+ | * https://ppc.cs.aalto.fi/ |