POLITECHNIKA KRAKOWSKA - WIEiK KATEDRA AUTOMATYKI I TECHNOLOGII INFORMACYJNYCH Przetwarzanie Równoległe i Rozproszone www.pk.edu.pl/~zk/prir_hp.html Wykładowca: dr inż. Zbigniew Kokosiński zk@pk.edu.pl
Wykład 16: Wprowadzenie do OpenMP Charakterystyka standardu OpenMP dla systemów SM Dyrektywy kompilatora języka sterujące zrównolegleniem obliczeń - region równoległy - równoległe pętle - nieiteracyjne konstrukcje rozdziału zadań do wątków - wykonanie jednowątkowe - skróty składniowe Wykonanie procedur środowiskowych Koordynacja i synchronizacja wątków - mechanizm zamykania (lock)
Charakterystyka systemu OpenMP OpenMP to przenośny standard programowania wielowątkowych systemów równoległych z dzieloną pamięcią (SM) OpenMP API zapewnia zbiór dyrektyw kompilatora, procedur bibliotecznych i zmiennych środowiskowych Dyrektywy OpenMP kompilatora rozszerzają sekwencyjne języki programowania (Fortran, C, C++) w konstrukcje SPMD, rozdziału zadań i pracy oraz konstrukcje synchronizacji OpenMP wspiera używanie danych prywatnych i współdzielonych, które są wyraźnie rozróżniane Procedury biblioteczne i zmienne środowiskowe sterują wykonaniem programu w systemie (runtime)
Charakterystyka systemu OpenMP cd. Model programowania OpenMP opiera się na współpracujących wątkach wykonywanych równocześnie na wielu procesorach lub rdzeniach (istnieje możliwość kompilacji do kodu sekwencyjnego) Wątki w równoległych regionach są tworzone i zamykane w oparciu o paradygmat fork-join (powstają w momencie pierwszego użycia parallel ) OpenMP powstał w 1997 (www.openmp.org) Większość kompilatorów wspiera OpenMP v. 2.5 (2005) OpenMP v. 3.1 (2011) wspiera kompilator gcc4.7 (a wcześniejsze wersje są wspierane od gcc4.2) oraz kompilatory Intel Fortran i C/C++
Dyrektywy kompilatora Program OpenMP potrzebuje plik <omp.h> w kompilatorze gcc opcja : - fopenmp kompilator wspierający OpenMP definiuje zmienną _OPENMP gdy w/w opcja jest aktywowana brak opcji oznacza, że w procesie translacji wszystkie dyrektywy OpenMP są ignorowane zmienna _OPENMP pomaga w poprawnej translacji do równoległego bądź sekwencyjnego kodu wynikowego W C/C++ dyrektywy z opcjonalnymi [klauzulami] podaje się za pomocą mechanizmu #pragma : #pragma omp directive [clauses [ ] ] { // structured block }
Obliczenia w regionie równoległym w pojedynczym wątku (master) występuje dyrektywa otwierająca równoległą część programu (region równoległy): #pragma omp parallel [clause [clause] ] { // structured block } każdy z utworzonych wątków otrzymuje unikalny numer, a wątek master ma numer 0 praca do wykonania pozostaje nierozdzielona ten sam kod jest wykonywany przez wszystkie wątki na możliwie różnych danych w modelu SPMD (numer wątku to thread id) pracę do wykonania można rozdzielić za pomocą innych dyrektyw
Obliczenia w regionie równoległym do uzyskania numeru wątku (thread id) służy procedura biblioteczna wywoływana przez dany wątek i zwracająca jego numer (w różnych regionach równoległych wątek może mieć różne numery) : int opm_get_thread_num() liczba wątków może być ustalona za pomocą klauzuli: num_threads (expression) procedura biblioteczna int opm_get_num_threads() zwraca liczbę wątków w regionie równoległym jako liczbę całkowitą, możliwą do wykorzystania w obliczeniach SPMD
Obliczenia w regionie równoległym na końcu regionu równoległego występuje domyślnie synchronizacja typu bariera po wyjściu z regionu równoległego obliczenia są kontynuowane tylko w wątku master klauzule dyrektywy parallel określają jakie dane wcześniej zadeklarowane są dla danego wątku prywatne a jakie dzielone pomiędzy wątki w regionie równoległym: private(list_of_variables) i shared(list_of_variables) ustawienia defaultowe : default(shared private) i default(none)
Obliczenia w regionie równoległym
Obliczenia w regionie równoległym możliwe jest zagnieżdżanie regionów równoległych poprzez wywołanie dyrektywy parallel w jednym z wątków aby zmienić defaultową liczbę wątków = 1 w zagnieżdżonym (wewnętrznym) regionie równoległym, należy zastosować funkcję biblioteczną: void omp_set_nested(int nested) z nested 0. rzeczywista liczba utworzonych wątków zależy od specyficznej implementacji OpenMP
Równoległe pętle for OpenMP posiada konstrukcje umożliwiające rozdział pracy pomiędzy wątki istniejące już w regionie równoległym Kontrukcja pętli równoległej (wzajemnie niezależne iteracje są przydzielane do wątków w regionie równoległym i wykonywane równolegle) : #pragma omp for [clause [clause] ] for (i = lower_bound; i op upper_bound; incr_expr) { } { // loop iterate } gdzie lower_bound, upper_bound wyrażenia całkowitoliczbowe; op operator boolowski ze zbioru {<,<=,>,>=} ; incr_expr postać: ++i, i++, --i, i--, i+= incr, i-= incr nowait klauzula znosząca domyślną synchronizację typu bariera (nie wolno używać break)
Równoległe pętle strategie szeregowania OpenMP wspiera różne strategie dystrybucji iteracji do wątków określone w parametrze schedule: schedule(static, block_size) przyporządkowanie statyczne typu round-robin bloków iteracji o ustalonym rozmiarze (defaultowo o wyrównanych rozmiarach) do dostępnych wątków schedule(dynamic, block_size) - przyporządkowanie dynamiczne kolejnego bloku iteracji o ustalonym rozmiarze do zwolnionego właśnie wątku (defaultowo o rozmiarze 1) schedule(guided, block_size) przyporządkowanie dynamiczne bloków iteracji o malejących rozmiarach. Dla block_size=1 rozmiar bloku jest wyznaczany każdorazowo jako stosunek nieprzyporząd-kowanych iteracji do liczby wątków w pętli równoległej. Dla block_size=k rozmiar bloku w wyjątkiem ostatniego nie jest mniejszy niż k, a dla nieokreślonej wartości block_size rozmiar bloku jest równy 1.
Równoległe pętle strategie szeregowania schedule(auto) szeregowanie pozostawione jest decyzji kompilatora i/lub systemowi runtime. schedule(runtime) szeregowanie pozostawione jest na czas wykonania programu, gdzie zależy od formatu zmiennej środowiskowej OMP_SCHEDULE : setenv OMP_SCHEDULE dynamic, 4 setenv OMP_SCHEDULE guided Gdy zmienna środowiskowa jest nieokreślona szeregowanie zależy od implementacji biblioteki OpenMP Przykład : mnożenie macierzy 100x100 : MAxMB=MC. MA, MB, MC zmienne dzielone; row, col, i zmienne prywatne szeregowanie typu static, bloki to wiersze (row)
Równoległe pętle mnożenie macierzy 1
Równoległe pętle mnożenie macierzy 2
Nieiteracyjne konstrukcje rozdziału zadań Kontrukcja sections służy do nieiteracyjnego rozdziału zadań do wątków wskazane niezależne fragmenty kodu są zadaniami do rozdziału pomiędzy różne wątki (synchronizacja sekcji implicite) : # pragma omp sections [clause [clause] ] { } [# pragma omp section] // w pierwszym bloku można pominąć { // structured block } [# pragma omp section ] { // structured block }. :
Wykonanie jednowątkowe Kontrukcja single służy do przyporządkowania zadania do jednego wątku (niekoniecznie master). Jest to użyteczne do zadań typu sterowanie komunikatami w czasie równoległego wykonania programu. # pragma omp single [Parameter [Parameter] ] { // structured block } Konstrukcja może być użyta wewnątrz regionu równoległego. Defaultowo na końcu występuje synchronizacja wątków. Kontrukcja master służy do przyporządkowania zadania do jednego wątku master : # pragma omp master { // structured block } Inne wątki ignorują tę konstrukcję. Wątek master nie jest synchronizowany z innymi wątkami.
Uproszczenia składni Open MP oferuje uproszczenia składni dla jednej konstrukcji for lub jednej sections (wszystkie klauzule parallel i for mogą być użyte): # pragma omp parallel for [clause [clause] ] { } for (i=lowed_bound; i op upper_bound; incr_expr) { // loop body } # pragma omp parallel sections [clause [clause] ] { [# pragma omp section] // można pominąć { // structured block } }
Wykonanie procedur środowiskowych Open MP zapewnia kilka procedur środowiskowych dla wykonania programów: Funkcja: void omp_set_dynamic (int dynamic_treads) Procedura : void omp_get_dynamic (void) Procedura: void omp_set_number_threads (int num_treads) Procedura: void omp_set_nested (int nested) Procedura: void omp_get_nested (int void)
Koordynacja i synchronizacja wątków Fragment programu z klauzulą reduction:
Koordynacja i synchronizacja wątków Fragment programu z konstrukcją flush:
Mechanizm synchronizacji locking Mechanizm locking Open MP opiera się na dwóch zmiennych, inicjalizowanych następująco : void omp_init_lock (omp_lock_t *lock); // simple lock void omp_nest_lock (omp_nest_lock_t *lock); // nested lock Dwa stany: locked, unlocked (początkowo: unlocked) Zmienna zamykana może służyć do synchronizacji wątków przez zamykanie/otwieranie: void omp_set_lock (omp_lock_t *lock); // simple lock void omp_set_nest_lock (omp_nest_lock_t *lock); // nested lock void omp_unset_lock (omp_lock_t *lock); // simple lock void omp_unset_nest_lock (omp_nest_lock_t *lock); // nested lock Zmienne mogą być zamykane/otwierane przez różne wątki. Zagnieżdżone zmienne mogą być zamykane przez ten sam wątek a ilość zamknięć zlicza wewnętrzny licznik.
Mechanizm synchronizacji locking W zagnieżdżonym zamknięciu unset zmniejsza stan licznika; gdy licznik wewnętrzny osiągnie stan 0 zmienna jest w stanie unlocked. Możliwe jest zamknięcie zmiennej bez blokowania wątku wywołującego: void omp_test_lock (omp_lock_t *lock); // simple lock void omp_test_lock (omp_nest_lock_t *lock); // nested lock Usuwanie zmiennej: void omp_destroy_lock (omp_lock_t *lock); // simple lock void omp_destroy_lock (omp_nest_lock_t *lock); // nested lock
Mechanizm locking
Literatura 1. Rauber T., Gudula R. : Parallel Programmming for Multicore and Cluster Systems, 2 ed. Springer 2012