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