This shows you the differences between two versions of the page.
apd:laboratoare:03 [2021/10/22 15:24] florin.mihalache [Căutarea binară paralelă] |
apd:laboratoare:03 [2023/10/08 16:32] (current) dorinel.filip move |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Laboratorul 3 - Algoritmi paraleli de sortare și de căutare ===== | ===== 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) { | + | |
- | for (var k = 0; k < list.length; k++) { | + | |
- | for (i = 0; i < list.length - 1; i += 2) { | + | |
- | if (list[i] > list[i + 1]) { | + | |
- | swap(list[i], list[i + 1]); | + | |
- | } | + | |
- | } | + | |
- | for (i = 1; i < list.length - 1; i += 2) { | + | |
- | if (list[i] > list[i + 1]) { | + | |
- | swap(list[i], list[i + 1]); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </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 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ă procesoarele 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 |}} | + | |
- | + | ||
- | ==== Căutarea binară paralelă ==== | + | |
- | Algoritmul de căutare binară paralelă se bazează pe împărţirea secvenţei în care se face căutarea în N subsecvenţe de aceeaşi lungime. La fiecare pas, fiecare din cele N procesoare verifică dacă o valoare x se află în subsecvența atribuită procesorului respectiv, prin compararea valorii lui x cu valorile aflate pe pozițiile start și end, care marchează capetele subsecvenței unui procesor. | + | |
- | + | ||
- | Dacă un procesor a găsit valoare x în subsecvența sa, acesta va anunța celelalte procesoare legat de faptul că x se află în subsecvența sa, iar apoi toate cele N procesoare vor căuta în subsecvența respectivă, care va fi împărțită în alte N subsecvențe, căutarea trecând la pasul următor. | + | |
- | + | ||
- | Spre deosebire de versiunea serială, unde se verifică dacă elementul căutat se află pe poziția mijlocie din secvență (să spunem poziția mid, iar pentru pozițiile de început și de sfârșit le spunem start și end), fapt care dovedește că elementul se află în secvență, la versiunea paralelizată se verifică dacă elementul se află pe una din pozițiile de start și de end din secvență (dacă se află pe una dintre aceste poziții, înseamnă că am găsit elementul în secvență). | + | |
- | + | ||
- | Se poate observa că operațiile de căutare a valorii x în subsecvențe de către fiecare procesor de la fiecare pas se pot realiza în paralel. Totuși, toate operațiile de căutare de la un pas trebuie să fie finalizate în totalitate înainte de trecerea la următorul pas, așadar în acest caz este nevoie de o barieră după fiecare pas de căutare. | + | |
- | + | ||
- | O reprezentare grafică a algoritmului de căutare binară paralelă se poate observa în imaginea de mai jos, unde operațiile cu aceeași culoare pot fi realizate în paralel, liniile de aceeași culoare reprezintă indicii de start și de end ai fiecărei subsecvențe, specifice fiecărui procesor, iar simbolurile cu roșu reprezintă bariere. | + | |
- | + | ||
- | {{ :apd:laboratoare:parallel-binary-search.png?direct&500 |}} | + | |
- | ==== Exerciții ==== | + | |
- | + | ||
- | - Pornind de la implementarea secvențială a algoritmului bubble sort aflată în fișierul **oets.c** din [[https://github.com/APD-UPB/APD/tree/master/laboratoare/lab03 | scheletul de laborator]], implementați și paralelizați algoritmul odd-event transposition sort. **(3p)** | + | |
- | - Analizați scalabilitatea implementării de la punctul precedent. Verificați **Hint 1** de mai jos pentru informații suplimentare. **(1p)** | + | |
- | - Pornind de la implementarea secvențială a algoritmului shear sort aflată în fișierul **shear.c** din arhiva de laborator, paralelizați algoritmul. **(3p)** | + | |
- | - Analizați scalabilitatea implementării de la punctul precedent. Verificați **Hint 1** de mai jos pentru informații suplimentare. **(1p)** | + | |
- | - Pornind de la implementarea secvențială a algoritmului merge sort aflată în fișierul **merge.c** din arhiva de laborator, paralelizați algoritmul. Verificați **Hint 2** de mai jos pentru informații suplimentare. **(2p)** | + | |
- | + | ||
- | <note tip> | + | |
- | **Hint 1** | + | |
- | + | ||
- | În toate fișierele sursă din arhiva de laborator, verificarea corectitudinii implementării paralele se realizează prin sortarea șirului folosind quick sort într-un array folosit ca etalon la final (se realizează o comparație element cu element). Atunci când doriți să verificați scalabilitatea programului vostru, comentați sau eliminați sortarea folosind quick sort și comparația aferentă, pentru a avea niște timpi cât mai corecți. De asemenea, încercați să eliminați toate afișările în terminal. | + | |
- | </note> | + | |
- | + | ||
- | <note tip> | + | |
- | **Hint 2** | + | |
- | + | ||
- | Implementarea merge sort din arhiva de laborator funcționează pentru un N putere a lui 2. Testați-vă implementarea paralelă doar pe acest caz. | + | |
- | </note> | + |