This shows you the differences between two versions of the page.
app:laboratoare:02 [2022/10/18 12:42] emil.slusanschi |
app:laboratoare:02 [2022/10/19 14:58] (current) emil.slusanschi [Clauza nowait] |
||
---|---|---|---|
Line 4: | Line 4: | ||
În OpenMP, când o structură de tip ''for'' este paralelizată fiecărui thread îi revine un număr egal de iterații din cadrul acelui for (aceasta este configuratia default). Uneori, se întâmplă ca iterațiile să fie echilibrate între ele în ceea ce privește workload-ul, alteori nu. Când workload-ul nu este echilibrat între thread-uri, pot apărea probleme în ceea ce privește performanțele programului - prin debalansarea incarcarii (load imbalance). | În OpenMP, când o structură de tip ''for'' este paralelizată fiecărui thread îi revine un număr egal de iterații din cadrul acelui for (aceasta este configuratia default). Uneori, se întâmplă ca iterațiile să fie echilibrate între ele în ceea ce privește workload-ul, alteori nu. Când workload-ul nu este echilibrat între thread-uri, pot apărea probleme în ceea ce privește performanțele programului - prin debalansarea incarcarii (load imbalance). | ||
- | Pentru a preveni situații când thread-urile au volume diferite de workload, există conceptul de scheduling în OpenMP. Pentru scheduling se folosește directiva ''schedule(tip_de_schedule, chunk_size)'', unde tipul de schedule poate fi: static, dynamic, guided, auto. Specificarea dimensiunii unui chunk este opționala. In cazul în care aceasta nu este specificată, acesta are o valoare default (in functie de tipul de schedule ales). | + | Pentru a preveni situații când thread-urile au volume diferite de workload, există conceptul de scheduling în OpenMP. Pentru scheduling se folosește directiva ''schedule(tip_de_schedule, chunk_size)'', unde tipul de schedule poate fi: static, dynamic, guided, auto. Specificarea dimensiunii unui chunk este opționala. In cazul în care aceasta nu este specificată, acesta are o valoare default (in functie de tipul de schedule ales). |
Exemplu de scheduling: | Exemplu de scheduling: | ||
Line 13: | Line 13: | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | Un exemplu grafic pentru cele trei tipuri de scheduling "clasic" poate fi vazut aici: | ||
+ | |||
+ | {{:app:laboratoare:openmp_scheduling.png?600|OpenMP Scheduling - RvdP@Sun}} | ||
+ | |||
+ | In cele ce urmeaza, vom lua pe rand cele trei tipuri de scheduling si le vom analiza individual. | ||
===== Static scheduling ===== | ===== Static scheduling ===== | ||
Line 70: | Line 76: | ||
Dacă setăm ''chunk_size'' cu 1, performanța este ilustrată în felul următor: | Dacă setăm ''chunk_size'' cu 1, performanța este ilustrată în felul următor: | ||
- | {{:app:laboratoare:parallel_for_uneven_static.png|Schedule cu chunk_size 1}} | + | {{:app:laboratoare:parallel_for_uneven_static.png?600|Schedule cu chunk_size 1}} |
În acest caz, thread-urile au un workload echilibrat, datorită distribuirii uniforme a iterațiilor din for, așadar avem o performanță bună. | În acest caz, thread-urile au un workload echilibrat, datorită distribuirii uniforme a iterațiilor din for, așadar avem o performanță bună. | ||
Dacă setăm ''chunk_size'' cu 2, performanța este ilustrată în felul următor: | Dacă setăm ''chunk_size'' cu 2, performanța este ilustrată în felul următor: | ||
- |  | + | {{:app:laboratoare:parallel_for_uneven_static2.png?600|Schedule cu chunk_size 2}} |
În acest caz, se observă un dezechilibru între thread-uri din punctul de vedere al workload-ului, implicând o performanță mai proastă decât în cazul când ''chunk_size'' este setat cu 1. | În acest caz, se observă un dezechilibru între thread-uri din punctul de vedere al workload-ului, implicând o performanță mai proastă decât în cazul când ''chunk_size'' este setat cu 1. | ||
Line 99: | Line 105: | ||
După fiecare rulare, împărțirea iterațiilor către thread-uri se schimbă. | După fiecare rulare, împărțirea iterațiilor către thread-uri se schimbă. | ||
- | Un exemplu grafic ar fi următorul: | + | Un exemplu fara niciun tip de schedule ar fi următorul: |
- | - aici nu folosim niciun tip de schedule: | + | {{:app:laboratoare:parallel_for_uneven_static2.png?600|Fara schedule}} |
- |  | + | |
- | - aici folosim dynamic schedule și se poate observa că nu există distribuție uniformă a iterațiilor către thread-uri: | + | |
- |  | + | |
- | De reținut faptul că dynamic schedule poate să nu fie mereu optim și este posibil ca static schedule să fie o soluție mai bună, cum ar fi în acest exemplu (static cu ''chunk_size = 1''): | + | Intr-un exemplu cu dynamic schedule se poate observa că nu există distribuție uniformă a iterațiilor către thread-uri: |
- |  | + | {{:app:laboratoare:parallel_for_random_dynamic.png?600|Schedule dynamic}} |
+ | |||
+ | De reținut faptul că dynamic schedule nu rezolva mereu problemele și este posibil ca static schedule să fie o soluție mai bună, cum ar fi în acest exemplu cu schedule static si ''chunk_size = 1'': | ||
+ | {{:app:laboratoare:parallel_for_random_static.png?600|Schedule random static}} | ||
===== Guided scheduling ===== | ===== Guided scheduling ===== | ||
Line 112: | Line 118: | ||
Guided scheduling se aseamănă cu dynamic schedule, în sensul că avem o împărțire pe chunks și o distribuire neuniformă a iterațiilor peste threadurile de executie. | Guided scheduling se aseamănă cu dynamic schedule, în sensul că avem o împărțire pe chunks și o distribuire neuniformă a iterațiilor peste threadurile de executie. | ||
- | Diferența față de dynamic schedule constă în dimensiunea chunk-urilor. Dimensiunea unui chunk este proporțională cu numărul de iterații neasignate în acel moment thread-ului împărțit la numărul de threads, la început un chunk putând avea dimensiunea ''nr_iterații / nr_threads'', ca pe parcurs să scadă dimensiunea acestuia. De exemplu la Valoarea la ''chunk_size'' (dacă nu avem, default este 1) reprezintă dimensiunea minimă pe care o poate avea un chunk (se poate ca ultimele chunk să aibă o dimensiune mai mică decât dimensiunea dată unui chunk). | + | Diferența față de dynamic schedule constă în dimensiunea chunk-urilor. Dimensiunea unui chunk este proporțională cu numărul de iterații neasignate în acel moment thread-ului împărțit la numărul de threads, la început un chunk putând avea dimensiunea ''nr_iterații / nr_threads'', ca pe parcurs să scadă dimensiunea acestuia. De exemplu la Valoarea la ''chunk_size'' (dacă nu avem, default este 1) reprezintă dimensiunea minimă pe care o poate avea un chunk (se poate ca ultimele chunk-uri să aibă o dimensiune mai mică decât dimensiunea dată unui chunk). |
Exemplu: | Exemplu: | ||
Line 138: | Line 144: | ||
</code> | </code> | ||
- | ===== nowait ===== | + | ===== Clauza nowait ===== |
Atunci când paralelizăm un for, există o barieră după fiecare for paralelizat, unde se așteaptă ca toate thread-urile din for-ul paralelizat să ajungă în același punct în același timp, ca apoi să-și continue execuția. | Atunci când paralelizăm un for, există o barieră după fiecare for paralelizat, unde se așteaptă ca toate thread-urile din for-ul paralelizat să ajungă în același punct în același timp, ca apoi să-și continue execuția. | ||
Line 145: | Line 151: | ||
#pragma omp parallel | #pragma omp parallel | ||
{ | { | ||
- | #pragma omp for nowait private(i) | + | #pragma omp for private(i) |
for (i = 0; i < 16; i++) { | for (i = 0; i < 16; i++) { | ||
c(i); | c(i); | ||
Line 154: | Line 160: | ||
În exemplul de mai sus, thread-urile din for-ul paralelizat trebuie să ajungă în același punct (să se sincronizeze), ca apoi să execute funcția ''d()''. | În exemplul de mai sus, thread-urile din for-ul paralelizat trebuie să ajungă în același punct (să se sincronizeze), ca apoi să execute funcția ''d()''. | ||
- | + | {{:app:laboratoare:parallel_for_4.png?600|Blocare pana la terminarea buclei for}} | |
- |  | + | |
Pentru a elimina această barieră / sincronizare, putem folosi directiva ''nowait'', prin care thread-urile nu mai așteaptă ca fiecare să ajungă în același punct (să fie sincronizate încât să fie în același punct în același timp), astfel un thread, după ce trece de for-ul paralelizat, trece imediat la execuția următoarelor instrucțiunilor, fără să mai aștepte după celelalte thread-uri. | Pentru a elimina această barieră / sincronizare, putem folosi directiva ''nowait'', prin care thread-urile nu mai așteaptă ca fiecare să ajungă în același punct (să fie sincronizate încât să fie în același punct în același timp), astfel un thread, după ce trece de for-ul paralelizat, trece imediat la execuția următoarelor instrucțiunilor, fără să mai aștepte după celelalte thread-uri. | ||
Line 171: | Line 176: | ||
</code> | </code> | ||
- |  | + | Rularea rezultata arata astfel: |
+ | {{:app:laboratoare:parallel_for_nowait.png?600|Rulare utilizand nowait}} | ||
- | ===== Reduction ===== | + | ===== Directiva 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, in mod automat sincronizat peste mai multe threaduri. Nu orice operatie poate fi utilizata intr-o operatie de tip ''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, in mod automat sincronizat peste mai multe threaduri. Nu orice operatie poate fi utilizata intr-o operatie de tip ''reduction''. | ||
Line 190: | Line 196: | ||
===== Exerciții ===== | ===== Exerciții ===== | ||
- | 1) Rulați [demo-urile](https://github.com/florinrm/app-laborator/tree/main/lab2/demo) legate de schedule și modificați valorile la ''chunk_size'', unde să faceți observații legate de performanțe. De asemenea, puteți schimba și tipul de schedule, pentru a observa eventuale schimbări în privința performanței. | + | 1) Rulați {{:app:laboratoare:demo.zip|Demo laborator 2}} legate de scheduling și modificați valorile la ''chunk_size'', unde să faceți observații legate de performanțe. De asemenea, puteți schimba și tipul de schedule, pentru a observa eventuale schimbări în privința performanței. |
- | 2) Paralelizați fișierul ''atan.c'' din [schelet](https://github.com/florinrm/app-laborator/tree/main/lab2/skel) folosind reduction. | + | 2) Paralelizați fișierul ''atan.c'' din {{:app:laboratoare:skel.zip|Schelet laborator 2}} folosind reduction. |
3) Rulați programul din fișierul ''schedule.c'' din schelet de mai multe ori și schimbați tipurile de schedule (cu tot cu ''chunk_size'') și observați performanțele. | 3) Rulați programul din fișierul ''schedule.c'' din schelet de mai multe ori și schimbați tipurile de schedule (cu tot cu ''chunk_size'') și observați performanțele. | ||
Line 198: | Line 204: | ||
4) Generați fișiere folosind scriptul ''gen_files.sh'' și paralelizați programul din fișierul ''count_letters.cpp'' din schelet. | 4) Generați fișiere folosind scriptul ''gen_files.sh'' și paralelizați programul din fișierul ''count_letters.cpp'' din schelet. | ||
- | ===== Resurse utile ===== | + | ===== Resurse ===== |
+ | * {{:app:laboratoare:demo.zip|Demo laborator 2}} | ||
+ | * {{:app:laboratoare:skel.zip|Schelet laborator 2}} | ||
+ | |||
+ | ===== Referinte ===== | ||
* http://jakascorner.com/blog/ | * http://jakascorner.com/blog/ | ||
* https://ppc.cs.aalto.fi/ | * https://ppc.cs.aalto.fi/ | ||
- | * [Dynamic scheduling vs. Guided scheduling](https://stackoverflow.com/questions/42970700/openmp-dynamic-vs-guided-scheduling) | + | * Dynamic vs. Guided Scheduling: https://stackoverflow.com/questions/42970700/openmp-dynamic-vs-guided-scheduling |
+ | * Laboratorul a fost construit de catre Florin Mihalache pe https://github.com/florinrm/app-laborator/ si portat/revizuit de catre Emil Slusanschi pe https://ocw.cs.pub.ro | ||