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