This shows you the differences between two versions of the page.
|
app:laboratoare:01 [2022/10/13 12:27] 127.0.0.1 external edit |
app:laboratoare:01 [2025/10/18 21:16] (current) tudor.calafeteanu [Resurse utile] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Laboratorul 01. ===== | + | ====== Laboratorul 1 - Introducere în 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 este creat pe baza modelului fork-join, unde avem un thread principal (master), din care se creează alte thread-uri (fork, echivalent cu ''pthread_create'' din pthreads), care, împreună cu thread-ul master, execută task-uri în paralel, în cadrul unor zone numite regiuni paralele. După ce task-urile respective sunt terminate, thread-urile forked "revin" în thread-ul principal (join). | ||
| + | |||
| + | {{ :app:laboratoare:fork_join.png?700 |}} | ||
| + | |||
| + | ====== Includere și compilare ====== | ||
| + | Pentru a putea folosi OpenMP în cod, trebuie inclusă biblioteca omp.h în cod: ''#include <omp.h>''. | ||
| + | |||
| + | Pentru compilare, este necesar un flag, care diferă în funcție de compilator: | ||
| + | * gcc: ''gcc helloworld.c -o helloworld -fopenmp'' | ||
| + | * SunStudio (pe fep): ''cc -xopenmp helloworld.c -o helloworld'' | ||
| + | |||
| + | ===== 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. | ||
| + | |||
| + | Tipar: ''#pragma omp numele_directivei [clause, ...]'' | ||
| + | Exemplu: ''#pragma omp parallel default(shared) private(beta, pi)'' | ||
| + | |||
| + | ==== 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. | ||
| + | |||
| + | <code c> | ||
| + | #pragma omp parallel | ||
| + | { | ||
| + | // cod paralelizat | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Exemplu de folosire: | ||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | #include <omp.h> | ||
| + | |||
| + | int main(int argc, char** argv) { | ||
| + | #pragma omp parallel | ||
| + | { | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("Hello world from thread number %d\n", tid); | ||
| + | } | ||
| + | |||
| + | #pragma omp parallel | ||
| + | { | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("Frumos in anul 4, zice thread-ul %d\n", tid); | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | De asemenea, putem avea regiuni paralele imbricate: | ||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | #include <omp.h> | ||
| + | |||
| + | int main(int argc, char** argv) { | ||
| + | #pragma omp parallel | ||
| + | { | ||
| + | printf("Parallel region thread %d\n", omp_get_thread_num()); | ||
| + | | ||
| + | #pragma omp parallel | ||
| + | { | ||
| + | printf("Nested parallel region thread %d\n", omp_get_thread_num()); | ||
| + | } | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ===== Funcții utile ===== | ||
| + | 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 cod: ''omp_set_num_threads(8)'' | ||
| + | Dacă dorim să măsurăm timpul de execuție al unei secvențe de cod paralelizat, putem folosi ''omp_get_wtime()''. Exemplu de folosire: | ||
| + | <code c> | ||
| + | t1 = omp_get_wtime(); | ||
| + | #pragma omp parallel | ||
| + | { | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("Hello world from thread number %d\n", tid); | ||
| + | } | ||
| + | t2 = omp_get_wtime(); | ||
| + | printf("Total execution time = %lf\n", (t2 - t1)); | ||
| + | </code> | ||
| + | |||
| + | Alte funcții utile: | ||
| + | * numărul de threads: ''omp_get_num_threads()'' | ||
| + | * id-ul thread-ului curent: ''omp_get_thread_num()'' | ||
| + | * numărul de procesoare: ''omp_get_num_procs()'' | ||
| + | |||
| + | ===== Vizibilitatea variabilelor ===== | ||
| + | Variabilele în cadrul blocurilor paralele pot fi: | ||
| + | * globale - văzute și partajate de toate thread-urile | ||
| + | * private - fiecare thread are variabilele sale private care nu sunt vizibile către alte thread-uri | ||
| + | |||
| + | În acest caz avem două clauze pentru context: | ||
| + | ''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)'') | ||
| + | Exemplu: | ||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | #include <omp.h> | ||
| + | |||
| + | int main(int argc, char** argv) { | ||
| + | int a = 6, b = 9, c = 10; | ||
| + | |||
| + | #pragma omp parallel private(a,b) shared(c) | ||
| + | { | ||
| + | // privates set the scope of variables | ||
| + | a = 1, b = 2, c = a + b; // cu private(a, b), aceste valori (la a si b) vor fi vizibile doar in acest bloc | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("In parallel block, in thread no %d: %d %d %d\n", tid, a, b, c); // printing 1 2 3 | ||
| + | } | ||
| + | printf("%d %d %d\n", a, b, c); // printing 6 9 3 | ||
| + | |||
| + | #pragma omp parallel shared(c) | ||
| + | { | ||
| + | a = 1, b = 2, c = a + b; | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("In parallel block, in thread no %d: %d %d %d\n", tid, a, b, c); // printing 1 2 3 | ||
| + | } | ||
| + | printf("%d %d %d\n", a, b, c); // printing 1 2 3 | ||
| + | |||
| + | #pragma omp parallel | ||
| + | { | ||
| + | a = 1, b = 2, c = a + b; | ||
| + | int tid = omp_get_thread_num(); | ||
| + | printf("In parallel block, in thread no %d: %d %d %d\n", tid, a, b, c); // printing 1 2 3 | ||
| + | } | ||
| + | printf("%d %d %d\n", a, b, c); // printing 1 2 3 | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | </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/ | ||