This shows you the differences between two versions of the page.
apd:laboratoare:03 [2020/10/22 10:48] radu.ciobanu Lab 3 APD |
apd:laboratoare:03 [2023/10/08 16:32] (current) dorinel.filip move |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 03 - Algoritmi paraleli de sortare ===== | + | ===== Laboratorul 3 - Algoritmi paraleli de sortare și de căutare ===== |
- | Responsabili: Radu Ciobanu, Andrei Damian, Delia Stuparu, Dragoș Cocîrlea | + | Documentația de laborator s-a mutat la [[https://mobylab.docs.crescdi.pub.ro/docs/parallelAndDistributed/introduction|această adresă]]. |
- | + | ||
- | În acest laborator, vom discuta despre câteva exemple de algoritmi paraleli de sortare. | + | |
- | + | ||
- | ==== Odd-even transposition sort (OETS) ==== | + | |
- | + | ||
- | Unul din cei mai cunoscuți (chiar dacă nu neapărat eficienți) algoritmi de sortare este **bubble sort**. Acesta funcționează pe baza următorului pseudocod: | + | |
- | + | ||
- | <code> | + | |
- | function bubbleSort(list) { | + | |
- | sorted = false; | + | |
- | while (!sorted) { | + | |
- | sorted = true; | + | |
- | for (var i = 0; i < list.length - 1; i++) { | + | |
- | if (list[i] > list[i + 1]) { | + | |
- | swap(list[i], list[i + 1]); | + | |
- | sorted = false; | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Așa cum se poate observa mai sus, bubble sort parcurge șirul de sortat element cu element, comparând elementul curent cu vecinul din dreapta. Dacă numărul din dreapta este mai mic, se realizează o interschimbare între elementul curent și cel din dreapta sa. Complexitatea acestui algoritm este **O(N<sup>2</sup>)**, pentru că se termină în cel mult N parcurgeri ale șirului (unde N este numărul de elemente din șirul de sortat). | + | |
- | + | ||
- | Un exemplu de comportament al bubble sort se poate observa în imaginea de mai jos. | + | |
- | + | ||
- | {{ :apd:laboratoare:bubble.png?direct&400 |}} | + | |
- | + | ||
- | Dacă am dori să paralelizăm acest algoritm, un potențial mod de abordare ar fi să realizăm în paralel comparația și potențiala interschimbare de elemente vecine, dar acest lucru ar putea duce la problema prezentată în imaginea de mai jos. | + | |
- | + | ||
- | {{ :apd:laboratoare:bubble_problem.png?direct&230 |}} | + | |
- | + | ||
- | Mai precis, operații pe elemente adiacente nu se pot realiza simultan, pentru că se poate ajunge la un race condition. Din acest motiv, un mod de a paraleliza bubble sort este dat de algoritmul **odd-even transposition sort**, care funcționează după următorul pseudocod: | + | |
- | + | ||
- | <code> | + | |
- | function oddEvenSort(list) { | + | |
- | sorted = false; | + | |
- | while (!sorted) { | + | |
- | sorted = true; | + | |
- | for (i = 0; i < list.length - 1; i += 2) { | + | |
- | if (list[i] > list[i + 1]) { | + | |
- | swap(list[i], list[i + 1]); | + | |
- | sorted = false; | + | |
- | } | + | |
- | } | + | |
- | for (i = 1; i < list.length - 1; i += 2) { | + | |
- | if (list[i] > list[i + 1]) { | + | |
- | swap(list[i], list[i + 1]); | + | |
- | sorted = false; | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Așa cum se poate observa mai sus, odd-even transposition sort are două faze. În **faza pară**, elementele de pe poziții pare din șirul de sortat sunt comparate (și eventual interschimbate) cu vecinii din dreapta. După ce se termină faza pară (adică după ce toate elementele pare au fost procesate), urmează **faza impară**, în care elementele impare sunt analizate și comparate cu vecinii din dreapta. La fel ca la bubble sort, numărul maxim de iterații necesare pentru a sorta un șir va fi N (numărul de elemente din șir). Dacă avem P fire de execuție, complexitatea acestui algoritm va fi **O(N/P*N)**, sau **O(N)** pentru P=N. În imaginea de mai jos, se poate observa o reprezentare grafică a modului de funcționare al odd-even transposition sort. | + | |
- | + | ||
- | {{ :apd:laboratoare:oets.png?direct&230 |}} | + | |
- | + | ||
- | <note important> | + | |
- | **Atenție!** Înainte de a se trece de la faza pară la faza impară și invers, este necesară sincronizarea folosind o barieră. Cu alte cuvinte, trebuie să ne asigurăm că toate thread-urile au terminat o fază înainte de a se trece la următoarea. | + | |
- | </note> | + | |
- | + | ||
- | <note tip> | + | |
- | Algoritmul odd-even transposition sort a fost gândit inițial pentru a fi rulat pe șiruri de procesoare ([[https://en.wikipedia.org/wiki/Massively_parallel_processor_array|processor arrays]]), unde un procesor conține o singură valoare din șirul de sortat și poate comunica doar cu procesorul din stânga și cu cel din dreapta. | + | |
- | </note> | + | |
- | + | ||
- | ==== Shear sort ==== | + | |
- | + | ||
- | Un alt exemplu de algoritm de sortare care a fost conceput pentru sisteme multi-procesor unde un procesor este conectat doar la o parte din celelalte procesoare este **shear sort** (cunoscut de asemenea ca **row-column sort** sau **snake-order sort**), care presupune că lucrăm pe procesoare conectate într-o formă de matrice. Astfel, un procesor poate să comunice cu vecinii din stânga, din dreapta, de sus și de jos. Dacă ne imaginăm deci că procesoarels sunt așezate într-o matrice, cele două faze ale algoritmului shear sort sunt următoarele: | + | |
- | + | ||
- | * se sortează liniile matricei astfel încât randurile pare au valorile ordonate crescător, iar rândurile impare au valorile ordonate descrescător | + | |
- | * se sortează coloanele crescător. | + | |
- | + | ||
- | Se garantează că algoritmul va sorta numerele după cel mult **sup(log<sub>2</sub>N) + 1** faze, unde N este numărul de elemente ce trebuie sortate. Din acest motiv, algoritmul are complexitatea **O(Nlog<sub>2</sub>N)**. Pseudocodul algoritmului este prezentat mai jos. | + | |
- | + | ||
- | <code> | + | |
- | function shearSort(matrix) { | + | |
- | for (k = 0; k < ceil(log2(matrix.lines * matrix.columns)) + 1; k += 2) { | + | |
- | for (i = 0; i < matrix.lines; i += 2) { | + | |
- | sortAscendingLine(i); | + | |
- | } | + | |
- | for (i = 1; i < matrix.lines; i += 2) { | + | |
- | sortDescendingLine(i); | + | |
- | } | + | |
- | for (var i = 0; i < matrix.columns; i++) { | + | |
- | sortAscendingColumn(i); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | O reprezentare grafică a funcționării shear sort este prezentată în figura de mai jos. | + | |
- | + | ||
- | {{ :apd:laboratoare:shear.png?direct&200 |}} | + | |
- | + | ||
- | <note tip>La finalul rulării algoritmului, lista de numere va fi sortată într-un mod „șerpuit”, de unde și numele algoritmului. Acest lucru se poate observa în imaginea de mai jos. | + | |
- | </note> | + | |
- | + | ||
- | {{ :apd:laboratoare:shear_output.png?direct&200 |}} | + | |
- | + | ||
- | ==== Merge sort paralel ==== | + | |
- | + | ||
- | **Merge sort** (sau sortarea prin interclasare) este un algoritm de sortare de tip **//divide et impera//** care presupune următorii pași generali: | + | |
- | + | ||
- | - se împarte șirul de N elemente de sortat în N șiruri de lungime 1 | + | |
- | - se aplica operația de interclasare („merge”) între câte două astfel de șiruri de lungime 1, rezultând N/2 șiruri sortate de lungime 2 | + | |
- | - se repetă pașii de mai sus realizând interclasări între șiruri din ce în ce mai mari, până se ajunge la un șir sortat de N elemente. | + | |
- | + | ||
- | Numărul de pași de interclasare necesari este log<sub>2</sub>N, iar operațiile de interclasare de la un pas se realizează în O(N), deci complexitatea algoritmului merge sort este **O(Nlog<sub>2</sub>N)**. | + | |
- | + | ||
- | Pentru a paraleliza acest algoritm, putem observa că operațiile de interclasare de la fiecare pas se pot realiza în paralel. Totuși, operațiile de „merge” de la fiecare pas trebuie terminate în totalitate înainte de a trece la următorul pas, deci avem nevoie de o barieră (sau un mecanism similar) după fiecare pas de interclasare. Se poate observa că gradul de paralelism de la un pas de interclasări este din ce în ce mai mic pe măsură ce avansăm în algoritm, pentru că numărul de operații de „merge” de la fiecare pas scade. Complexitatea algoritmului paralel este **O(N)** pentru P=N. | + | |
- | + | ||
- | O reprezentare grafică a algoritmului de merge sort paralel se poate observa în imaginea de mai jos, unde operațiile cu aceeași culoare pot fi realizate în paralel, iar simbolurile cu roșu reprezintă bariere. | + | |
- | + | ||
- | {{ :apd:laboratoare:parallel_merge.png?direct&400 |}} | + | |
- | + | ||
- | ==== Exerciții ==== | + | |
- | + | ||
- | - 1 | + | |
- | - 2 | + | |
- | - 3 | + | |
- | - 4 | + | |
- | - 5 | + |