Komunikacja zbiorowa: część II Najczęstsze błędy i wypaczenia przy stosowaniu komunikacji zbiorowej Grupy procesów i podział komunikatorów Operacje na grupach procesów Komunikacja wewnątrzgrupowa i komunikatory wewnętrzne Komunikacja międzygrupowa i komunikatory zewnętrzne
Błędy i wypaczenia w komunikacji zbiorowej Wywołanie procedury komunikacji zbiorowej tylko dla części procesorów zawartych w zaspecyfikowanym komunikatorze. W szczególności zapomnienie o tym, że w przypadku MPI_Bcast zarówno procesor źródłowy jak i wszyscy odbiorcy muszą zawołać tę procedurę. Założenie, że wszystkie procesory jednocześnie zakończą wykonywanie danej procedury komunikacji zbiorowej. Użycie tego samego bufora wejściowego i wyjściowego w wywołaniu MPI_Reduce (MPI_Allreduce).
1 i 2. Wywołanie w zależności od procesora (BARDZO POWAŻNY błąd, bo nie powoduje zatrzymania programu i przez to znakomicie utrudnia jego odrobaczenie). if (rank == 0) { MPI_Bcast(dane, ..., Master, ..., MPI_COMM_WORLD); do_masters_work(); else { do_work(dane,wyniki); MPI_Gather(wyniki,...,Master,...,MPI_COMM_WORLD); } W takim przypadku master wyśle dane do pozostałych procesorów ale dane te nigdy nie zostaną odebrane, natomiast wyniki wysłane przez robotników nigdy nie zostaną odebrane przez mastera. Należy napisać tak: else
4. Użycie tego samego bufora wejściowego i wyjściowego w MPI_Reduce call MPI_Allreduce(a, a, 1, MPI_REAL, MPI_SUM, comm, ierr); Spowoduje to natychmiastowy bład wykonania i przerwanie programu, więc błąd taki jest łatwy do wykrycia. Wymóg, aby bufor wyjściowy był różny od wyjściowego zapewnia kompatybilność ze standartem FORTRANu (przekazywanie zmiennej przez adres a nie przez wartość); w związku z tym ograniczenie obowiązuje również przy wołaniu MPI_Reduce w programach w C. Poprawny kod: call MPI_Allreduce(a, a_sum, 1, MPI_REAL, MPI_SUM, comm, ierr);
Grupy procesów i podział komunikatorów Grupa procesów: zbiór procesorów uporządkowany według rzędu. W MPI zdefiniowano „pustą” grupę: MPI_GROUP_EMPTY oraz dwie stałe: MPI_GROUP_EMPTY (odnośnik do grupy pustej) oraz MPI_GROUP_NULL (odnośnik do grupy zdefiniowanej niewłaściwie). Podział komunikatorów: Komunikatory wewnętrzne (intrakomunikatory): służą do komunikacji w obrębie grupy; możliwa jest komunikacja punktowa i zbiorowa. Komunikatory zewnętrzne (interkomunikatory): służą do komunikacji pomiędzy dwoma grupami. W standarcie MPI-1 możliwa jest jedynie komunikacja punktowa (MPI_Send i MPI_Receive).
Charakterystyka intra- i interkomunikatorów Funkcjonalność Intrakomunikator Interkomunikator Liczba grup 1 2 Bezpieczeństwo Tak Komunikacja zbiorowa Nie Topologie Kaszowanie
Operacje na grupach MPI_GROUP_SIZE(group, size): zwraca liczbę procesorów w grupie. MPI_Group_size(MPI_Group group, int *size) MPI_GROUP_SIZE(GROUP, SIZE, IERROR) INTEGER GROUP, SIZE, IERROR group - grupa size - liczba procesorów w grupie MPI_GROUP_RANK(group, rank): zwraca rząd danego procesora w grupie; jeżeli procesor nie jest w grupie jest zwracany rząd MPI_UNDEFINED. group - grupa rank - rząd procesora w grupie. MPI_Group_rank(MPI_Group group, int *rank) MPI_GROUP_RANK(GROUP, RANK, IERROR) INTEGER GROUP, RANK, IERROR
MPI_GROUP_TRANSLATE_RANKS(group1, n, ranks1, group2, ranks2) podaje rzędy procesorów z jednej grupy w innej grupie; w przypadku, gdy któryś z procesorów nie należy do drugiej grupy zwraca w tym miejscu MPI_UNDEFINED. group1 - grupa pierwsza n - liczba procesorów w tablicach ranks1 i ranks2 ranks1 - tablica rzędów w grupie pierwszej group2 - grupa druga ranks2 - tablica rzędów w grupie drugiej MPI_Group_translate_ranks (MPI_Group group1, int n, int *ranks1, MPI_Group group2, int *ranks2) MPI_GROUP_TRANSLATE_RANKS(GROUP1, N, RANKS1, GROUP2, RANKS2, IERROR) INTEGER GROUP1, N, RANKS1(*), GROUP2, RANKS2(*), IERROR
MPI_GROUP_COMPARE(group1, group2, result) porównuje dwie grupy zwracając wynik w zmiennej result: MPI_IDENT: grupy są identyczne; MPI_SIMILAR: grupy zawierają te same procesory ale w innym porządku; MPI_UNEQUAL: grupy są różne.
Kreatory grup MPI_COMM_GROUP(comm, group) uzyskiwanie wskaźnika grupy (group) odpowiadającej komunikatorowi comm. MPI_Comm_group(MPI_Comm comm, MPI_Group *group) MPI_COMM_GROUP(COMM, GROUP, IERROR) INTEGER COMM, GROUP, IERROR
Trzy standardowe operacje mnogościowe: MPI_GROUP_UNION(group1, group2, newgroup) MPI_GROUP_INTERSECTION(group1, group2, newgroup) MPI_GROUP_DIFFERENCE(group1, group2, newgroup)
MPI_GROUP_INCL(group, n, ranks, newgroup) tworzenie nowej grupy z elementów starej. group - stara grupa; n - liczba procesorów ze starej grupy, które mają być włączone do nowej; ranks - tablica zawierająca te rzędy; newgroup - nowa grupa. MPI_Group_incl(MPI_Group group, int n, int *ranks, MPI_Group *newgroup) MPI_GROUP_INCL(GROUP, N, RANKS, NEWGROUP, IERROR) INTEGER GROUP, N, RANKS(*), NEWGROUP, IERROR MPI_GROUP_EXCL(group, n, ranks, newgroup) tworzy nową grupę ze starej poprzez wyłączenie n procesorów o rzędach podanej w tablicy ranks. Składnia analogiczna do MPI_GROUP_INCL.
MPI_GROUP_RANGE_INCL(group, n, ranges, newgroup) MPI_GROUP_RANGE_EXCL(group, n, ranges, newgroup) Wygodniejsze formy poprzednich. Tablica ranges(3,*) zawiera zakresy włączanych/wyłączanych procesorów; ranges(1,i) i ranges(2,i) definiują odpowiednio pierwszy i ostatni procesorów w i-tym zakresie, ranges(3,i) mówi z jakim krokiem włącza/wyłącza się procesory (1 - każdy, 2 - co drugi, itd.). Destruktor grupy MPI_GROUP_FREE(group) MPI_Group_free(MPI_Group *group) MPI_GROUP_FREE(GROUP, IERROR) INTEGER GROUP, IERROR
Komunikacja wewnątrzgrupowa i komunikatory wewnętrzne Komunikacja wewnątrzgrupowa: wymiana informacji w obrębie procesorów należących do jednej grupy. Możliwe jest tutaj stosowanie zarówno procedur komunikacji punktowej jak i zbiorowej.
Operacje na komunikatorach MPI_COMM_SIZE(comm, size) MPI_COMM_RANK(comm, rank) MPI_COMM_COMPARE(comm1, comm2, result) Wartości zwracane przez MPI_COMM_COMPARE w zmiennej result są podobne jak w przypadku MPI_GROUP_COMPARE z wyjątkiem, że w przypadku identyczności grup odpowiadających komunikatorom comm1 i comm2 zwracana jest wartość MPI_CONGRUENT. Standardowe komunikatory MPI_COMM_WORLD: wszystkie procesory przydzielone zadaniu; MPI_COMM_SELF: dany procesor (zawsze ma rząd 0). MPI_COMM_NULL: pusty komunikator.
Kreatory komunikatorów MPI_COMM_DUP(comm, newcomm) tworzenie duplikatu komunikatora. comm - stary komunikator; newcomm - nowy komunikator. MPI_Comm_dup(MPI_Comm comm, MPI_Comm *newcomm) MPI_COMM_DUP(COMM, NEWCOMM, IERROR) INTEGER COMM, NEWCOMM, IERROR
MPI_COMM_CREATE(comm, group, newcomm) tworzenie („pączkowanie”) nowego komunikatora. comm - stary komunikator (może być MPI_COMM_WORLD); group - grupa procesorów, które mają utworzyć nowy komunikator; newcomm - nowy komunikator. int MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm) MPI_COMM_CREATE(COMM, GROUP, NEWCOMM, IERROR) INTEGER COMM, GROUP, NEWCOMM, IERROR
MPI_COMM_SPLIT(comm, color, key, newcomm) tworzenie nowych komunikatorów poprzez podział starego według przypisania zawartego w zmiennej color. comm - stary komunikator; color - kolor; procesory o różnych kolorach będą tworzyły różne komunikatory; key - klucz; wskazuje, jak ma wzrastać rząd procesorów (jeżeli dla dwóch procesorów w nowej grupie jest taki sam, kolejność rzędów jest tak jak kolejność rzędów w starej grupie); newcomm - nowy komunikator, w którym znajdzie się wywołujący procesor. Jeżeli nie chcemy, żeby dany procesor znalazł się w jakimkolwiek z nowych komunikatorów, nadajemy mu kolor MPI_UNDEFINED. MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm) MPI_COMM_SPLIT(COMM, COLOR, KEY, NEWCOMM, IERROR) INTEGER COMM, COLOR, KEY, NEWCOMM, IERROR
Rozważmy grupę procesorów a-j o rzędach (w starym komunikatorze) od 0-9 (┴ oznacza kolor MPI_UNDEFINED): rząd 1 2 3 4 5 6 7 8 9 procesor a b c d e f g h i j kolor klucz ┴ ┴ Wywołanie MPI_COMM_SPLIT dla tego układu spowoduje powstanie trzech nowych komunikatorów: {f,g,a,d} (kolor 0); rzędy będą wynosiły odpowiednio 0, 1, 2, 3. {e,i,c} (kolor 3); rzędy będą wynosiły 0, 1, 2. {h} (kolor 5); rząd oczywiście 0. Procesory b oraz j nie będą należały do żadnego z komunikatorów.
Destruktor komunikatora MPI_COMM_FREE(comm) MPI_Comm_free(MPI_Comm *comm) MPI_COMM_FREE(COMM, IERROR) INTEGER COMM, IERROR
Przykład: Obliczanie liczby p metodą Monte Carlo Algorytm obliczania liczby p: losujemy pary liczb (x,y) należące do przedziału [-1..1] a następnie obliczamy przybliżenie ze stosunku liczby wylosowanych punktów leżących w kole scentrowanym w punkcie (0,0) i o promieniu 1 do liczby wszystkich wylosowanych punktów.
Dedykujemy procesor o najwyższym rzędzie jako serwer liczb losowych dla pozostałych procesorów. Z pozostałych procesorów tworzymy grupę przy pomocy procedury MPI_GROUP_EXCL oraz MPI_COMM_CREATE grupę „robotników” i definiujemy odpowiedni komunikator workers. „Robotnicy” obliczają ile przysłanych losowych punktów leży w kole (N_in) a ile poza nim (N_out), następnie wykorzystując procedurę MPI_ALLREDUCE działająca w obrębie komunikatora workers. Jeżeli obliczone przybliżenie liczby różni się od wartości prawdziwej o mniej niż zadeklarowaną dokładność (pobierana z linii polecenia) lub przekroczono maksymalną zadeklarowaną liczbę kroków, program się kończy. Jeżeli nie, każdy z „robotników” wysyła do serwera żądanie inicjalizacji generatora liczb losowych i procedura jest powtarzana od punktu 3. Kod źródłowy programu w języku C Wyniki (4 procesory)
Przykład zastosowania procedury MPI_Comm_split Źródło programu w C Wyniki
Komunikacja międzygrupowa Komunikacja międzygrupowa: wymiana informacji pomiędzy procesami należącymi do rozłącznych grup. Komunikację międzygrupową stosuje się dla zadań modularnych, gdzie informacje muszą przepływać pomiędzy kolejnymi grupami (rura) lub, w bardziej ogólnym przypadku, płynąć po grafie zdefiniowanym przez interkomunikatory.
Cechy komunikacji międzygrupowej: Można stosować jedynie procedury komunikacji punktowej. Pomiędzy grupami procesów definiuje się skierowane interkomunikatory; zbiór interkomunikatorów tworzy graf połączeń. Procesy identyfikuje się poprzez ich rzędy w lokalnych grupach.
Kreator interkomunikatorów MPI_INTERCOMM_CREATE(local_comm, local_leader, bridge_comm, remote_leader, tag, newintercomm) local_comm - komunikator lokalny local_leader - rząd procesora będącego „bazą” komunikatora lokalnego; musi być w bridge_comm bridge_comm - komunikator mostkujący; musi zawierać procesory obu łączonych grup (na ogół MPI_COMM_WORLD) remote_leader - rząd procesora-bazy komunikatora odległego (w bridge_comm) tag – „pieczątka” interkomunikatora newintercomm - nowo postały interkomunikator
MPI_Intercomm_create(MPI_Comm local_comm, int local_leader, MPI_Comm bridge_comm, int remote_leader, int tag, MPI_Comm *newintercomm) MPI_INTERCOMM_CREATE(LOCAL_COMM, LOCAL_LEADER, PEER_COMM, REMOTE_LEADER, TAG, NEWINTERCOMM, IERROR) INTEGER LOCAL_COMM, LOCAL_LEADER, PEER_COMM, REMOTE_LEADER, TAG, NEWINTERCOMM, IERROR
Tworzenie intrakomunikatora z dwóch interkomunikatorów MPI_INTERCOMM_MERGE(intercomm, high, newintracomm) intercomm - interkomunikator high - kolejność procesorów w tworzonej grupie newintracomm - utworzony intrakomunikator MPI_Intercomm_merge(MPI_Comm intercomm, int high, MPI_Comm *newintracomm) Sprawdzanie, czy komunikator jest intra- czy interkomunikatorem MPI_COMM_TEST_INNER(comm, flag) comm - komunikator flag - flaga; true jeżeli komunikator jest interkomunikatorem MPI_Comm_test_inter(MPI_Comm comm, int *flag)
Uzyskiwanie rozmiaru, rzędu procesora oraz grupy odpowiadającej „lokalnej” części interkomunikatora: MPI_COMM_RANK (comm, rank) MPI_COMM_SIZE (comm, size) MPI_COMM_GROUP (comm, group) Uzyskiwanie rozmiaru oraz grupy odpowiadającej „odległej” części interkomunikatora: MPI_COMM_REMOTE_SIZE (comm, size) MPI_COMM_REMOTE_GROUP (comm, group)
Przykład: Symulacja układu ocean: atmosfera call MPI_COMM_SIZE( MPI_COMM_WORLD, nprocs, ierr ) call MPI_COMM_RANK( MPI_COMM_WORLD, rank, ierr ) if (rank .lt. Size/2 ) then color = OCEAN else color = ATMOS endif call MPI_COMM_SPLIT( MPI_COMM_WORLD, color, rank, ocean_or_atmos_comm, ierr) call MPI_INTERCOMM_CREATE( ocean_or_atoms_comm, 0, MPI_COMM_WORLD, 0, 0, intercomm, ierr) if (color .eq. OCEAN) then ocean_comm = ocean_or_atmos_comm call do_ocean( ocean_comm ) atmos_comm = ocean_or_atmos_comm call do_atmos( atmos_comm ) call ocean_and_atmos( intercomm )
Przykład bardziej konkretny: przepływ danych pomiędzy grupami procesorów („rura”) Grupę 12 procesorów dzielimy na 3 podgrupy po 4 procesory w każdej, jak na rysunku. Dane są wymieniane w obie strony pomiędzy grupami 0 i 1 oraz 1 i 2. W tym celu dla grup 0 i 2 musimy zdefiniować po jednym komunikatorze, natomiast dla grupy 1 musimy zdefiniować 2 interkomunikatory - odpowiednio do komunikacji z grupą 0 i 2.
Źródło programu w C Wyniki (12 procesorów)