Implementacja metody gradientów sprzężonych dla klastrów

Transkrypt

Implementacja metody gradientów sprzężonych dla klastrów
Implementacja metody gradientów sprz˛eżonych dla
klastrów wielordzeniowych i wieloprocesorowych
z wykorzystaniem jednostronnych operacji
komunikacji standardu MPI-2
Zygmunt Ptak1
1 Wydział Inżynierii Mechanicznej i Informatyki
Kierunek Informatyka, Rok V
[email protected]
Streszczenie
Głównym celem prezentacji jest przedstawienie wyników wykorzystania jednostronnych operacji komunikacji standardu MPI-2. Jako algorytm podstawowy do implementacji równoległej zastosowano metod˛e gradientu sprz˛eżonego. Całość poddano porównaniu wyników czasowych z kodem napisanym przy wykorzystaniu standardowych operacji komunikacji MPI, oraz zastosowaniem technologii PThread, OpenMP.
Również b˛eda˛ przedstawione wady i zalety zastosowania wykorzystanych w/w komunikacji jednostronnych.
1
Wst˛ep
Zagadnienia symulujace
˛ zjawiska fizyczne wymagaja˛ obliczania układów równań liniowych. Do rozwiazywania
˛
układów równań jest wiele metod, mi˛edzy innymi takie jak
1
metody dokładne . Oraz iteracyjne, które rozwiazanie
˛
jest obliczane za każdym razem w
p˛etli z pewnym przybliżeniem. Do momentu w którym dane rozwiazanie
˛
uznamy za właściwe, lub z jakiegoś powodu zostanie zatrzymane, np. przerwanie przez użytkownika,
badź
˛ wystapi
˛ dzielenie przez liczba˛ bliska˛ zeru.
Czas obliczenia na jednym procesorze może być czasochłonny. Aby zagadnienie zostało w czasie szybciej obliczone, stosuj˛e si˛e tworzenie algorytmów równoległych potrafiacych
˛
wykonać ta˛ sama˛ operacj˛e na wielu procesorach badź
˛ wielu komputerach posiadajace
˛ różna˛ ilość procesorów.
Niezb˛edna˛ operacja˛ do działania takiego algorytmu jest możliwość przesyłania danych
pomi˛edzy działajacymi
˛
procesami, badź
˛ watkami.
˛
Uwarunkowane to jest podziałem danych, oraz obliczanym pewnym fragmentem przez dany proces. Standard MPI gwarantuje
nam różne funkcje przesyłania danych. Jedna˛ z nich sa˛ tzw. jednostronne operacje komunikacji (osc - ang. One-Sided Communications). Standard MPI-2 określa, iż zaprojektowane zostały z myśla˛ stworzenia funkcji w których programiści (biblioteki[2],[1] opartej
1 dokładne
z punktu widzenia matematycznego
1
o standard MPI) moga˛ wykorzystać funkcje do szybszego przesyłania danych w zależności od docelowej platformy sprz˛etowej. Mogłyby do nich należeć mi˛edzy innymi mechanizmy takie jak: mechanizmy pami˛eci rozproszonej, lub współdzielonej; wykorzystanie
kanałów DMA, sprz˛etowe operacje PUT i GET. Dla sprawdzenia, który ze sposobów b˛edzie szybszy, czyli przesyłanie danych za pomoca˛ jednostronnych operacji komunikacji,
transfer danych z wykorzystaniem standardowych operacji, lub za pomoca˛ watków
˛
które
już maja˛ możliwość współdzielenia zasobów, został sporzadzony
˛
zestaw algorytmów w
C, stosujacy
˛ wyżej wymienione standardy.
2
Cel pracy
Celem pracy jest implementacja metody gradientów sprz˛eżonych dla klastrów wieloprocesorowych i wielordzeniowych z wykorzystaniem:
• Standardu MPI-2:
– Przesyłanie danych pomi˛edzy procesami za pomoca˛ standardowych operacji
transferu danych
– Przesyłanie danych pomi˛edzy komputerami (w˛ezłami) tak jak w podpunkcie
poprzednim. Lecz z ta˛ różnica,˛ że transfer mi˛edzy procesami uruchomionymi
na procesorze wielordzeniowym b˛eda˛ inicjowane za pomoca˛ operacji jednostronnych.
• Standardu MPI-2 i niżej wymienionych:
– Standardu OpenMP[3]: czyli wewnatrz
˛ w˛ezła nast˛epuje rozdzielenie operacji
mi˛edzy watkami,
˛
kod watków
˛
b˛edzie tworzony przez kompilator, z prostymi
podpowiedziami dla niego zapisanymi w dyrektywach preprocesora
– Standardu PThread[4]: tak jak w punkcie poprzednim, wykorzystujac
˛ watki
˛
POSIX’owe
Całość zostanie przetestowana na Clusterix’ie. Jako zagadnienie podstawowe posłuży zagadnienie przepływu ciepła. Ostatecznie wyniki zostana˛ zestawione i sporzadzone
˛
wnioski.
3
Algorytm Gradientów Sprz˛eżonych
Obecnie najszybsza metoda rozwiazuj
˛ aca
˛ algebraiczne liniowe układy równań postaci:
Ax = b
Stworzona przez Magnusa R. Hestens’a i Eduarda Stiefel’a w 1952 roku[6] na podstawie
metody Najwi˛ekszego Spadku, oraz metody Kierunków Sprz˛eżonych. Wada˛ tej metody
jest wymaganie postawione macierzy A, która powinna być:
• symetryczna wzgl˛edem diagonali
• dodatnio określona, czyli zachowywała warunek nierówności: xT Ax > 0
2
Natomiast jej zaleta˛ jest oczywiście szybkość zbieżności do rozwiazania,
˛
oraz łatwość
tworzenia na jej podstawie równoległego kodu programu. Jest to możliwe dzi˛eki podstawowym działaniom arytmetycznym m.in. takim jak: mnożeniu macierzy przez wektor,
iloczynem skalarnym, dodawaniu i odejmowaniu wektorów. Nie musimy macierzy sprowadzać np. do postaci trójkatnej,
˛
i zaciemniać kodu programu tym przygotowaniem.
Algorytm CG ma nast˛epujac
˛ a˛ postać:
d(0) = r(0) = b − Ax(0)
α(i) =
T r
r(i)
(i)
T Ad
d(i)
(i)
x(i+1) = x(i) + α(i) d(i)
r(i+1) = r(i) − α(i) Ad(i)
β(i+1) =
T
r(i+1)
r(i+1)
T r
r(i)
(i)
d(i+1) = r(i+1) + β(i+1) d(i)
Gdzie duże litery to macierze, małe to wektory, a litery grackie to skalary.
4
Tworzenie watków
˛
Watki
˛ można tworzyć dzi˛eki różnym standardom i bibliotekom, które sa˛ dostarczane przez
różnych producentów. Do testów zastosowano watki
˛ POSIX’owe[4], oraz watki
˛ generowane przez kompilator dzi˛eki standardowi OpenMP[3].
5
5.1
Operacje jednostronne standardu MPI-2
Podstawowe informacje
Operacje jednostronne poniżej sa˛ cz˛eścia˛ standardu MPI-2[5]. Podstawowym „tunelem”
do wymiany danych sa˛ okna (MPI_Win). Do przesyłania danych służa˛ operacj˛e MPI_Put,
MPI_Get (tzw. operacje nieblokujace).
˛
Synchronizacja nast˛epuje poprzez funkcje:
• MPI_Win_fence
• MPI_Win_start, MPI_Win_complete, MPI_Win_post, MPI_Win_wait
• MPI_Win_lock, MPI_Win_unlcok
Funkcje inicjalizujace
˛ operacj˛e sa˛ nieblokujace,
˛ dzi˛eki takiemu podejściu możemy wykonywać jakaś
˛ operacj˛e np. mnożenie macierzy przez wektor, a w tle pobiera dane z jakiegoś
miejsca w pami˛eci innego procesu.
3
5.2
Przykład stosujacy
˛ operacje jednostronne
Poniżej został przedstawiony przykład z wykorzystaniem operacji jednostronnych. W poniższym przykładzie zależy nam, aby każdy proces zapisał swój numer w tablicy procesu
zerowego. W pierwszym przykładzie wykorzystamy operacj˛e MPI_Put i każdy proces
zapisz˛e własny numer. W drugim przykładzie proces zerowy pobierze sobie od każdego
procesu wartość za pomoca˛ operacji MPI_Get.
5.2.1
Tworzenie okna
Każdy proces ma zapisany numer w wartości rank. Proces zerowy tworzy tablic˛e tab, i
do niej b˛eda˛ zapisywane wartości przesłane od innych procesów, wi˛ec na podstawie tej
tablicy tworzy okno. Procesy pozostałe tworza˛ okno w oparciu o wartość rank, ponieważ ta˛ wartość bedzie pobierał proces zerowy. Proces tworzenia okna polega na podaniu wskaźnika do bufora danych, wielkości bufora w bajtach, tzw wartości display, jest
to wartość typu podstawowego naszego bufora w bajtach, flag informacji MPI- przyj˛eto MPI_INFO_NULL, interkomunikatora - MPI_COMM_WORLD, oraz wskaźnika do
okna, gdzie zostanie zapisane chwytak do naszego okna.
MPI_Win win ;
i f ( ! rank )
MPI_Win_create (
tab , size * s i z e o f ( i n t ) , s i z e o f ( i n t ) ,
MPI_INFO_NULL , MPI_COMM_WORLD , & win ) ;
else
MPI_Win_create ( & rank , s i z e o f ( i n t ) , s i z e o f ( i n t ) ,
MPI_INFO_NULL , MPI_COMM_WORLD , & win ) ;
MPI_Win_fence ( 0 , win ) ;
5.2.2
Inicjalizacja transferu od strony każdego procesu: MPI_Put
Pierwszy przykład w którym każdy proces przesyła swój numer do procesu zerowego. Do
funkcji przekazywany jest: wskaźnik z danymi które zostana˛ przesłane & rank; ilość czyli jeden element; typ danych MPI_INT; numer procesu do którego przesyłamy dane;
wartości określonej w standardzie jako display, służy ona do określenia komórki poczat˛
kowej bufora od której ma zaczać
˛ zapisywanie; kolejna wartość to ilość elementów odbieranych; typ danych odbieranych; oraz okno na którym dokonujemy operacji przesłania.
i f ( rank )
MPI_Put ( & rank , 1 , MPI_INT , 0 ,
rank , 1 , MPI_INT , win ) ;
else
* tab = 0 ;
5.2.3
Inicjalizacja transferu od strony procesu zerowego: MPI_Get
Przykład ten przedstawia podejście z drugiej strony, czyli pobranie danych przez proces
zerowy. Operacja MPI_Get pobiera takie same parametry jak funkcja MPI_Put. Z ta˛ różnica,˛ że adres docelowy służy do zapisania, danych, a nie do odczytu jak miało to miejsce
w pierwszym przypadku.
4
i f ( ! rank ) {
* tab = 0 ;
f o r ( i = 1 ; i < size ; ++i )
MPI_Get ( tab + i , 1 , MPI_INT ,
i , 0 , 1 , MPI_INT , win ) ;
}
5.3
Synchronizacja danych
Ważnym elementem programów równoległych jest synchronizacja procesów (watków),
˛
oraz aktualizacja danych. Czyli pobranie pewnej badź
˛ całej cz˛eści danych z innego procesu. W funkcji gradientu sprz˛eżonego wywoływana jest funkcja która synchronizuje wektor ’d’.
Poniżej zamieszczono przykłady wywoływania funkcji, w różnych metodach równoległego kodu programu:
/ / OpenMP :
i f ( ! thread_id )
Sync_vector ( g_d , e ) ;
#
pragma omp b a r r i e r
/ / PThread :
i f ( ! thread_id )
Sync_vector ( g_d , e ) ;
pthread_barrier_wait ( b2 ) ;
/ / MPI ( o s c ) :
Sync_vector_osc ( dg , from , lsize , size , e , win_dg ) ;
Funkcja Sync_vector(. . . ) synchronizuje dane mi˛edzy procesami głównymi (na każdy w˛ezeł przypada jeden). Skoro watki
˛ współdziela˛ dane, nie ma potrzeby synchronizacji danych pomi˛edzy nimi. Inaczej ma si˛e to w przypadku funkcji Sync_vector_osc(. . . ).
Wewnatrz
˛ niej musza˛ zostać wykonane operacje synchronizujace
˛ dane mi˛edzy procesami
uruchomionymi na w˛ezłach, później nast˛epuje synchronizacja mi˛edzy hostami, i ponownie procesy mog˛e pobrać aktualna˛ cz˛eść danych od głównego procesu. Wn˛etrze funkcji
wyglada
˛ nast˛epujaco:
˛
v o i d Sync_vector_osc ( Real * v , Int from , Int lsize , Int size ,
envinfo_t * e , MPI_Win win )
{
i f ( ! envinfo_is_root ( e−>host ) )
MPI_Put ( v+from , lsize , LOCAL_REAL , e−>host−>root ,
from , lsize , LOCAL_REAL , win ) ;
MPI_Win_fence ( 0 , win ) ;
i f ( envinfo_is_root ( e−>host ) )
Sync_vector_osc_sub ( v , size , e ) ;
MPI_Win_fence ( 0 , win ) ;
i f ( ! envinfo_is_root ( e−>host ) )
MPI_Get ( v , size , LOCAL_REAL , e−>host−>root , 0 ,
size , LOCAL_REAL , win ) ;
MPI_Win_fence ( 0 , win ) ;
}
5
v o i d Sync_vector_osc_sub ( Real * v , Int v_size , envinfo_t * e )
{
Int i , j ;
i f ( envinfo_is_root ( e−>host ) )
{
f o r ( i = 0 ; i < e−>host_count ; ++i )
{
i f ( e−>hosts_info [ i ] . root == e−>rank )
f o r ( j = 0 ; j < e−>host_count ; ++j )
{
i f ( e−>hosts_info [ j ] . root == e−>rank )
continue ;
MPI_Send (
v + e−>hosts_info [ i ] . from ,
e−>hosts_info [ i ] . size ,
LOCAL_REAL ,
e−>hosts_info [ j ] . root ,
TAG_SYNC_VECTOR ,
e−>comm
);
}
i f ( e−>hosts_info [ i ] . root == e−>rank )
continue ;
MPI_Recv (
v + e−>hosts_info [ i ] . from ,
e−>hosts_info [ i ] . size ,
LOCAL_REAL ,
e−>hosts_info [ i ] . root ,
TAG_SYNC_VECTOR ,
e−>comm ,
& status
);
}
}
}
6
6.1
Testy i wyniki
Przeprowadzono obliczenia na podstawie zagadnienia przepływu
ciepła
Podstawowe dane wejściowe:
• Przepływ ciepła ustalony, z warunkami brzegowymi Dirichleta
• Wartości temperatury na brzegach:
10, 10, 0, −15, oraz wartość temperatury poczatkowej
˛
elementu wynosi 0.
• Dokładność wyników rozwiazanych
˛
metoda˛ gradientów sprz˛eżonych wynosi 1e-3
• Zagadnienie dwuwymiarowe.
Zadanie liczone na klastrze zbudowanym z siedmiu w˛ezłów, z którego dwa składały si˛e
z dwóch procesorów, pozostałe po jednym. Każdy z procesorów złożony jest z czterech
rdzeni. W przypadku programów nie wykorzystujacych
˛
watków
˛
POSIX’owych na każdy rdzeń przypadał jeden proces. W programach z podziałem na watki,
˛
na każdy w˛ezeł
przypadał jeden główny proces, nast˛epnie w nim nast˛epował podział na watki.
˛
Celem
6
takiego podziału jest przydzielenie każdemu rdzeniu procesora jednego procesu (watku)
˛
liczacego.
˛
6.2
Wyniki
Zestawienie czasowe
180000
MPI
OSC
OpenMP
PThread
Alg.Sekwencyjny
160000
140000
Czas [s]
120000
100000
80000
60000
40000
20000
0
50x
100
50
200
x10
x20
0
0
300
x30
0
400
x
400
500
x
500
600
x
600
700
x
700
800
x80
0
900
x90
0
100
0x1
000
Rozmiar siatki
Rys. 1: Zestawienie czasowe dla odpowiednich siatek i algorytmów
Zestawienie przyspieszenia
30
MPI
OSC
OpenMP
PThread
Przysieszenie
25
20
15
10
5
0
50x
50
100
x10
0
200
x20
0
300
400
500
600
700
800
900
100
x30
x40
x50
x60
x70
x80
x90
0x1
0
0
0
0
0
0
0
000
Rozmiar siatki
Rys. 2: Zestawienie przyspieszenia dla odpowiednich siatek i algorytmów równoległych
7
Wyniki zostały zestawione w postaci wykresów na rys. 1, oraz na rys. 2. Przyspieszenie algorytmów liczone na podstawie stosunku czasu obliczania algorytmu sekwencyjnego do czasu obliczania algorytmu równoległego.
Rozmiar siatek oznacza ilość w˛ezłów w poziomie i w pionie. Aby przeliczyć ilość niewiadomych układu równań, należy przemnożyć odpowiednie wartości.
6.3
Podsumowanie
Z wykresów można wywnioskować to co można było przypuszczać już na samym poczat˛
ku. Jeżeli jakieś procesy współdziela˛ dane, i nie przesyłaja˛ ich mi˛edzy soba,˛ to zyskujemy
dodatkowy czas. Watki
˛ czyli lekkie procesy, współdziela˛ dane, i jedyne co musi zostać
przesłane to dane pomi˛edzy maszynami. Czyli tzw. pami˛ecia˛ rozproszona.˛
Zwraca nam uwag˛e również czas wykonania programu wykorzystujacego
˛
operacje
jednostronne (OSC). Jest on wi˛ekszy niż czas wykonania kodu w standardzie MPI-2 w
którym wszystkie procesy synchronizuja˛ dane za pomoca˛ funkcji MPI_Send, MPI_Recv.
Mimo, że operacje jednostronne moga˛ korzystać z kanałów DMA, to w tym przypadku
(dokładnie dla metody CG), dane musza˛ zostać skopiowane do celu i musimy poczekać
aż to kopiowanie si˛e zakończy. Gdybyśmy w tym momencie mogli wykonywać jakieś
inne operacje, z pewnościa˛ przyspieszenie byłoby widoczne. Można również zadać sobie
pytanie, czemu czas jest wi˛ekszy, a nie przynajmniej równy. Powodem tej sytuacji jest
wi˛eksza ilość punktów synchronizacji. Tracimy milisekundy które sa˛ tracone na zatrzymanie i wybudzenie procesu do dalszego działania. Z pewnościa˛ operacje jednostronne
maja˛ sens gdy można w tle wykonywać kopiowanie. Jeżeli nie ma potrzeby korzystania z
nich, wybór w jaki sposób dane b˛eda˛ synchronizowane zależy od programisty. Jeżeli nie
chcemy zaciemniać kodu śmiało możemy przyjać
˛ jeden styl przesyłania danych.
Literatura
[1] User’s Guide, Installer’s Guide, http://www.mcs.anl.gov/research/projects/mpich2/documentation/index.php?s=docs,
[2] LAM/MPI User’s Guide, LAM/MPI Installation Guide, http://www.lam-mpi.org/using/docs,
[3] OpenMP Application Program Interface (version 3.0), 2008, http://openmp.org,
[4] POSIX Threads Programming, https://computing.llnl.gov/tutorials/pthreads,
[5] MPI: A Message Passing Interface Standard (Version 2.2), 2009, www.mpiforum.org/docs/mpi2-report.pdf,
[6] Magnus R. Hestenes, Eduard Stiefel, Methods of Conjugate Gradients for Solvng
Linear Systems, 1952, http://nvl.nist.gov/pub/nistpubs/jres/049/6/V49.N06.A08.pdf,
8