Równoleg le sortowanie przez scalanie
Transkrypt
Równoleg le sortowanie przez scalanie
Równolegle sortowanie przez scalanie Bartosz Zieliński 1 Zadanie Napisanie programu sortuja̧cego przez scalanie tablicȩ wygenerowanych losowo liczb typu double w którym każda z procedur scalania odbywa siȩ w innym procesie. Tablica do posortowania, jak również pomocnicza tablica (liczb uint), umieszczone sa̧ w dzielonym segmencie pamiȩci. Procesy synchronizuja̧ swoje dzialanie przy pomocy semaforów i komunikatów ipc. Podtablice o rozmiarze mniejszym od ustalonego powinny być sortowane przez wybór (najmniejszego elementu) w pojedyńczym procesie, zamiast bycia dalej dzielonymi i scalanymi. Dziȩki dodatkowej tablicy można uczynić z wyjściowej tablicy listȩ jednokierunkowa̧. Program powinien 1. Wylosować a nastȩpnie wypisać wylosowana̧ tablicȩ liczb double. 2. Dokonać niezbȩdnych inicjalizacji. 3. Rozwidlić siȩ na tyle procesów ile jest potrzebnych scaleń i sortowań przez wybór. 4. Posortować tablicȩ w możliwie najbardziej równolegly sposób. 5. Wypisać na ekranie posortowana̧ tablicȩ. 6. Pozostawić po sobie porza̧dek, tzn. żadnych zombi ani nieużywanych obiektów ipc po zakończeniu programu. Program wzorcowy mial ok. 300 linii. Szczególy i wskazówki poniżej. 2 Tablice jako listy jednokierunkowe Zaleta̧ list jest możliwość wstawiania lub usuwania elementów z wnȩtrza listy lokalnie. Gdy chcemy wstawić albo usuna̧ć elementy z wnȩtrza tablicy, tak aby zachować kolejność pozostalych elementów musimy przesuna̧ć wszystkie pozostale elementy stoja̧ce po prawej stronie usuwanego/wstawianego elementu. Jeśli jednak zależy nam na tym 1 aby elementy listy przechowywane byly w pamiȩci dzielonej nie możemy przydzielać im pamiȩci przy pomocy funkcji malloc() zwracaja̧cej wskaźnik na pocza̧tek bloku pamiȩci znajduja̧cej siȩ w niedzielonej stercie. Rozwia̧zaniem jest przechowywanie wartości które chcemy ulożyć w listȩ w tablicy T elementów typu double w segmencie dzielonym, oraz zastosowanie pomocniczej tablicy (również w segmencie dzielonym) NEXT elementów uint. Aby otrzymać listȩ należy teraz zapisać w tablicy NEXT indeksy elementów w tablicy T, tak by T[NEXT[i]] byl elementem nastȩpuja̧cym bezpośrednio w otrzymanej liście po elemencie T[i]. Na przyklad tablice T i NEXT podane w poniższej tabelce: i T[i] NEXT[i] 0 1 2 3 4 5 6 7 # 15.1 2.0 12.0 100.1 16.7 5.1 8.2 4 5 1 0 7 3 2 6 reprezentuja̧ listȩ 100.1 → 8.2 → 5.1 → 2.0 → 15.1 → 16.7 → 12.0 → NULL Wartość T[0] jest niewykorzystywana, a NEXT[0] przechowuje indeks glowy listy. Dziȩki temu, ponieważ pod indeksem 0 nie jest przechowywana żadna wartość (sta̧d #), można użyć 0 jako NULL, tzn. NEXT[i]==0 oznacza że T[i] jest ostatnim elementem listy. Aby usuna̧ć z listy element nastȩpuja̧cy po T[i] wystarczy wykonać NEXT[i]=NEXT[NEXT[i]]; Nastȩpuja̧cy kod wstawia element T[j] za elementem T[i]: NEXT[j]=NEXT[i]; NEXT[i]=j; 3 Sortowanie list przez scalanie Dana jest lista L do posortowania i rozmiar listy poniżej którego powinno siȩ stosować inny algorytm: 1. Podziel listȩ na dwie polowy. Otrzymujemy dwie listy L1 ,L2. 2. Jeśli rozmiar L1 jest mniejszy od podanego to sortuj inna̧ metoda̧ (tutaj: przez wybór). W przeciwnym wypadku sortuj L1 przez scalanie. 3. Jeśli rozmiar L2 jest mniejszy od podanego to sortuj inna̧ metoda̧ (tutaj: przez wybór). W przeciwnym wypadku sortuj L2 przez scalanie. 4. Scal L1 i L2. Scalanie L1 i L2 do listy L: 2 1. Pocza̧tkowo L jest pusta. 2. Powtarzaj 3-6 dopóki L1 lub L2 jest niepusty. 3. Jeśli L1 pusty to k = 2,w przeciwnym przypadku jeśli L2 pusty lub glowa(L1) ≤ glowa(L2) to k = 1, a jeśli glowa(L2) < glowa(L1) to k = 2. 4. x = wartość w glowie(Lk) 5. Usuń pierwszy element z Lk. 6. Wstaw x na końcu L. Innymi slowy sortowanie przez scalanie można zilustrować na przykladzie sortowania talii kart wedlug wartości: 1. Podziel taliȩ na dwa stosy. 2. Posortuj każdy ze stosów. 3. Pola̧cz oba stosy, wybieraja̧c za każdym razem mniejsza̧ z kart leża̧cych na szczytach obu stosów. 4 Sortowanie list przez wybór Dana jest lista L do posortowania, listȩ wynikowa̧ oznaczmy L0 . 1. List L0 jest pusta. 2. Znajdź najmniejszy element x w liście L. 3. Usuń x z L. 4. Wstaw x na koniec L0 . 5. Powtarzaj 2-4 dopóki lista L jest niepusta. 5 Pelne drzewa binarne Dowolna sekwencja liczb od 1 do n ma naturalna̧ strukturȩ pelnego drzewa binarnego. Pelne drzewo binarne to drzewo binarne w którym wszystkie poziomy (za wyja̧tkiem być może ostatniego) sa̧ wypelnione. Ostatnia warstwa w drzewie nie musi być pelna, ale nie może mieć dziur: innymi slowy jeśli w ostatniej warstwie pojawia siȩ NULL to musi być NULL do końca. Rys. 1 i 2 powinny wyjaśnić niejasności. 3 nn nnn n n nn nnn n n n w nn 2 A A AA AA AA 4 .. ... . 8 1 LL LLL LLL LLL LLL & 9 5 00 00 00 10 11 3 44 44 44 6 7 12 Rys.1 Przyklad pelnego drzewa binarnego r rrr r r r rrr r r x r r 4 .. ... . 8 2 A A AA AA AA 9 1 LL LLL LLL LLL LLL & 5 10 6 11 3 A A AA AA AA 7 00 00 00 12 13 Rys.2 To nie jest pelne drzewo binarne. Oczywiście ponumerować można wȩzly każdego drzewa. Rzecz w tym że w przypadku pelnego drzewa binarnego, dla wȩzla nr i numery wȩzlów jego rodzica i prawego i lewego dziecka (jeśli istnieja̧) można otrzymać przy pomocy bardzo prostych wzorów: PARENT(i) = bi/2c, LEFT(i) = 2i, RIGHT(i) = 2i + 1. (1) Wzory te okaża̧ sie potrzebne w tym zadaniu ponieważ nasz program bȩdzie rozwidlal siȩ rekurencyjnie tworza̧c pelne drzewo binarne (każdy wȩzel to proces) a do każdego procesu poza korzeniem przypisany bȩdzie semafor. Podobnie do każdego procesu poza procesami z najniższego rzȩdu przypisany bȩdzie typ komunikatu. Zarówno typ komunikatu jak i nr semafora w tablicy to kolejne liczby i bȩdziemy stosować wzory (1) do przypisania semafora i rodzaju komunikatu do procesu. 6 Rozwidlanie i synchronizacja Pocza̧tkowo kolejność elementów w liście do posortowania pokrywa siȩ z kolejnościa̧ elementów w tablicy T, tzn. NEXT[i]=i+1. Jeśli rozmiar tablicy do posortowania wynosi N to proces glówny rozdziela pocza̧tkowa̧ listȩ elementów na polowȩ, wykonuja̧c instrukcjȩ NEXT[N/2]=0; 4 Mamy teraz dwie listy w podtablicach T[1..N/2] i T[N/2+1..N]. Program glówny rozwidla siȩ dwukrotnie, a jego dzieci sortuja̧ podtablice odpowiednio T[1..N/2] i T[N/2+1..N]. Dzieci rozwidlaja̧ siȩ dalej rekurencyjnie. Rozwidlanie kończy to pokolenie które otrzymuje do posortowania tablicȩ o wymiarze mniejszym od ustalonego wymiaru MIN SIZE. Proces który otrzyma do postortowania podtablicȩ o wymiarze mniejszym od MIN SIZE wykonuje sortowanie przez wstawianie bez dalszego rozwidlania siȩ. Proces który rozwidlil siȩ scala listy posortowane przez jego dzieci. W tym celu dzieci przesylaja̧ przy pomocy kolejki komunikatów indeksy elementów które stana̧ siȩ glowami posortowanych podlist. Pocza̧tkowo glowa̧ podlisty byl pierwszy element podtablicy, ale o ile nie byl on elementem najmniejszym, po posortowaniu bȩdzie to jakiś inny element. Należy zwrócić uwagȩ na fakt że oba algorytmy: sortowania przez scalanie i przez wybór, buduja̧ posortowana̧ listȩ w taki spoób że kolejny element dola̧czany jest do wynikowej listy zawsze na końcu i nie zmienia potem swojej pozycji. Z kolei algorytm scalania potrzebuje w każdym kroku tylko pocza̧tkowych elementów scalanych podlist. Sta̧d jeśli procesy sortuja̧ce bȩda̧ sygnalizowly swoim rodzicom umieszczenie na wlaściwym miejscu kolejnego elementu sortowanej podlisty, proces rodzic bȩdzie mógl wykonać scalenie kolejnego elementu bez czekania na zakończenie procesu dzieci. Przypomina to problem producenta i konsumenta. Szczególy powinny zostać wyjaśnione przez przyklad na Rys. 3 i 4. S qq qqq q q q x qq q S “ S “ P4 ” T [1..12] T [13..25] .. .. .. “ P1 ” T [1..50] T [51..100] NNN NNN NNN NN& P2 ” T [1..25] T [26..50] S << << << < S “ “ P5 ” T [26..37] T [38..50] S .. .. .. “ P6 ” T [51..62] T [63..75] .. .. .. P3 ” T [51..75] T [76..100] == == == = S “ P7 ” T [76..87] T [88..100] 00 00 00 P8 P9 P10 P11 P12 P13 P14 P15 W (T [1..12]) W (T [13..25]) W (T [26..37]) W (T [38..50]) W (T [51..62]) W (T [63..75]) W (T [76..87]) W (T [88..100]) Rys.3 Przyklad rozwidlenia na procesy sortuja̧ce gdy ilość elementów do posortowania wynosi N = 100 a rozmiar MIN SIZE poniżej którego stosuje siȩ sortowanie przez wybór to 20. S oznacza scalanie podanych podtablic, W sortowanie podtablicy przez wybór. 5 Semafory Z każdym procesem poza P1 zwia̧zane sa̧ semafory S0 , . . . , S13 , i.e z procesem Pi zwia̧zany jest semafor Si−2 . Proces Pi sygnalizuje semafor Si−2 gdy umieści na wlaściwym miejscu element sortowanej przez siebie podlisty. Z kolei proces Pi jeśli wykonuje sortowanie przez scalanie czeka pod semaforami SLEFT(i)−2 lub SRIGHT(i)−2 przed scaleniem kolejnych elementów z list sortowanych przez procesy PLEFT(i) i PRIGHT(i) . Typy komuniktów Z procesami P1 , . . . , P7 , tzn. z każdym procesem wykonuja̧cym sortowanie przez scalanie, zwia̧zany jest typ komunikatu. Typem komunikatu zwia̧zanego z procesem Pi jest i. Używana jest pojedyńcza kolejka komunikatów ipc. Proces Pi wysyla do kolejki komunikat typu PARENT(i) z informacja̧ o indeksie glowy sortowanej przez niego podtablicy, i.e. o indeksie najmniejszego elementu w podtablicy. Proces PARENT(i) odbiera komunikaty od swoich dzieci, dziȩki czemu może rozpocza̧ć scalanie. Rys.4 Semafory i kolejki komunikatów w przykladzie z Rys. 3. 6