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 [2025/10/18 21:16] (current) tudor.calafeteanu [Resurse utile] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Laboratorul 1 - Introducere în OpenMP ===== | + | ====== Laboratorul 1 - Introducere în OpenMP ====== |
| - | ==== 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). | + | |
| - | Î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 8: | ||
| {{ :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 15: | ||
| * 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 21: | ||
| 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 71: | ||
| </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 92: | ||
| * 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 102: | Line 98: | ||
| În acest caz avem două clauze pentru context: | În acest caz avem două clauze pentru context: | ||
| - | ''SHARED'' - variabilă partajată între thread-uri (exemplu: ''SHARED(c)'') | + | ''SHARED'' - variabilă partajată între thread-uri (exemplu: ''<nowiki>SHARED(c)</nowiki>'') |
| ''PRIVATE'' - variabilă văzută doar de thread-ul respectiv în blocul paralelizat (exemplu: ''PRIVATE(a, b)'') | ''PRIVATE'' - variabilă văzută doar de thread-ul respectiv în blocul paralelizat (exemplu: ''PRIVATE(a, b)'') | ||
| Exemplu: | Exemplu: | ||
| Line 140: | Line 136: | ||
| } | } | ||
| </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 ===== | ||
| + | * [[https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf | OpenMP 4.5 API Specifications]] | ||
| + | * [[https://www.openmp.org/wp-content/uploads/OpenMP-4.5-1115-CPP-web.pdf | OpenMP 4.5 Reference Guide – C/C++]] | ||
| + | * [[https://www.openmp.org/wp-content/uploads/openmp-examples-4.5.0.pdf | OpenMP 4.5 Examples]] | ||
| + | * http://jakascorner.com/blog/ | ||
| + | * https://ppc.cs.aalto.fi/ | ||