MPI

Poradnik
Konto użytkownika  • Poczta elektroniczna  • Korzystanie z SSH  • Systemy kolejkowe: PBS (Klaster halo2), LoadLeveler (BlueGene/P notos, Power775 boreasz), SLURM (Klaster hydra, Klaster GPU grom)
Programowanie
Kompilatory: C/C++, Fortran  • Programowanie równoległe: OpenMP, MPI, UPC, CAF, SHMEM, pthreads  • Biblioteki numeryczne: BLAS, LAPACK, FFTW
Optymalizacja
Uruchamianie i optymalizacja kodów na architekturze Blue Gene/P  • Uruchamianie i optymalizacja kodów na halo2
Krok po kroku
Logowanie do ICM (Windows)  • Logowanie do ICM (UNIX)  • MPI (Klaster halo2)  • MPI (BlueGene/P notos)
Wszystkie "Krok po kroku"
Dokumentacja

Spis treści

Wprowadzenie

W programowaniu równoległym mamy wiele podejść do kwestii wymiany danych między poszczególnymi procesami obliczeniowymi, w ramach jednego zadania równoległego. Począwszy od modeli, w których każdy proces ma bezpośredni dostęp do pamięci wszystkich pozostałych procesów (rozwiązanie rzadko spotykane w praktyce, poza komputerami o pamięci współdzielonej fizycznie), poprzez bezpośredni dostęp tylko do części danych, deklarowanych jako współdzielone (przykładem jest Co-Array Fortran, opisywany w poprzednim biuletynie), aż do modeli, w których nie ma bezpośredniego dostępu do pamięci innych procesów obliczeniowych, a wszelka wymiana danych z innymi procesami musi zachodzić przy współpracy stron, poprzez wymianę komunikatów.

MPI, czyli Message Passing Interface, jest przykładem ostatniego podejścia. Procesy obliczeniowe są uruchamiane oddzielnie na każdym węźle obliczeniowym lub procesorze, a do komunikacji służą wywołania funkcji biblioteki komunikacyjnej MPI.

Biblioteka MPI może być używana z poziomu C oraz Fortranu. Według mojego rozeznania, większa liczba użytkowników ICM używa Fortranu, z tego powodu wszelkie przykłady w artykule są podane dla tego języka. Użycie biblioteki MPI z C jest bardzo podobne -- zmienia się sposób podawania niektórych argumentów funkcji MPI, ze względu na różnice między Fortranem i C.

Istnieją 2 ważne wersje standardu MPI: 1.1 oraz 2.0. Wersja 2.0 usprawnia wiele mechanizmów oraz umożliwia dynamiczne zarządzenie procesami. Ze względu na wstępny charakter tego artykułu, skupimy się na wersji 1.1 standardu.

Podstawy

Tworzenie programu używającego biblioteki MPI różni się nieco od używania innych, normalnych bibliotek. Na etapie kodowania jest to używanie zwykłych wywołań procedur MPI. Ponieważ w efekcie chcemy uzyskać program równoległy, kompilacja oraz uruchamianie wygląda nieco inaczej, używa się do tego kompilatora mpif77 lub mpif90, do uruchamiania takich zadań służą programy mpirun oraz mpiexec.

Zacznijmy od kodowania. Jak już wiemy, na każdym węźle obliczeniowym/procesorze uruchomiony jest oddzielny proces. W celu użycia biblioteki MPI, musimy dokonać jej inicjalizacji na początku programu:

call MPI_Init(ierr)

Parametr ierr zawiera status wykonania procedury (w tym artykule, ze względu na wstępny charakter, pominięto analizę sytuacji wyjątkowych).

Podobnie przed zakończeniem wykonania programu musimy poinformować MPI o zakończeniu pracy:

call MPI_Finalize(ierr)

Pozwólmy teraz procesowi obliczeniowemu zorientować się w otoczeniu: ile procesów uczestniczy w obliczeniach, oraz jaki jest wśród nich numer danego procesu:

call MPI_Comm_Rank(MPI_Comm_World,imy_rank,ierr)
call MPI_Comm_Size(MPI_Comm_World,inp,ierr)

Pierwsza procedura pozwala poznać numer danego procesu imy_rank, druga -- liczbę procesów inp. Parametr MPI_Comm_World informuje MPI, że jesteśmy zainteresowani danymi globalnymi (całego świata) -- MPI umożliwia również tworzenie mniejszych grup procesorów, w celu ułatwienia wykonywania pewnych operacji.

Komunikacja

Zajmijmy się teraz komunikacją. Możliwości jest wiele:

  • komunikacja punkt-punkt asynchroniczna
  • komunikacja punkt-punkt synchroniczna
  • komunikacja grupowa: wysyłanie z jednego węzła do wszystkich pozostałych w grupie, zbieranie wartości ze wszystkich węzłów w grupie na jednym, wymiana danych każdy z każdym itp.

Użycie synchronicznej komunikacji punkt-punkt polega na wywołaniu, odpowiednio w dwóch procesach, następujących procedur:

call MPI_Send(x, 1, MPI_Real, t, tag, MPI_Comm_World, ierr)
call MPI_Recv(x, 1, MPI_Real, f, tag, MPI_Comm_World, istatus, ierr)

gdzie znaczenie poszczególnych parametrów jest następujące:

  • x -- zmienna do wysłania, typu Real
  • 1 -- długość danych, w tym wypadku pojedyncza liczba
  • MPI_Real -- określenie typu wysyłanych danych
  • f oraz t -- odpowiednio, numer procesora źródłowego oraz docelowego
  • tag -- znacznik komunikatu (możliwe jest odbieranie wiadomości tylko o zadanej wartości znacznika)
  • MPI_Comm_World -- określenie grupy komunikacyjnej, w której otrzymaliśmy numer procesu
  • ierr -- wynik działania procedury
  • istatus -- otrzymany status komunikacji

Komunikacja synchroniczna, mimo że wymaga spotkania się czasowego procesów, jest łatwiejsza do opanowania -- nie musimy sobie radzić z przepełnianiem buforów następującym w komunikacji asynchronicznej. Jakikolwiek błąd szybko prowadzi zazwyczaj do zablokowania się programu w oczekiwaniu na komunikację, co ułatwia jego zaobserwowanie.

Pisanie złożonych programów przy użyciu komunikacji punkt-punkt jest możliwe, jednak uciążliwe. Znacznie praktyczniejsze jest użycie procedur komunikacji grupowej. Najczęściej używane jest rozsyłanie przez jeden proces danych do wszystkich pozostałych w grupie, a następnie, po pewnych obliczeniach, gromadzenie wszystkich wyników:

call MPI_Bcast(x, 1, MPI_Integer, 0, MPI_Comm_World, ierr)        
call MPI_Reduce(r, rw, 1, MPI_Real, MPI_SUM, 0, MPI_Comm_World, ierr)
  • x -- zmienna do wysłania, typu Integer
  • r -- zmienna zawierająca dla każdego procesu liczby do scalenia, typu Real
  • rw -- zmienna zawierająca wynik scalania
  • 1 -- długość danych, w tym wypadku pojedyncza liczba
  • MPI_Real/MPI_Integer -- określenie typu wysyłanych danych
  • 0 -- numer procesora rozsyłającego i zbierającego dane
  • MPI_Comm_World -- określenie grupy komunikacyjnej, w której otrzymaliśmy numer procesu
  • ierr -- wynik działania procedury
  • MPI_SUM -- rodzaj operacji wykonywanej przy zbieraniu wyników, w tym wypadku sumowanie

W przypadku programowania z użyciem komunikacji synchronicznej, raczej nie potrzebujemy używać synchronizacji procesów. Jeśli jednak chcemy to zrobić, możemy użyć procedury:

call MPI_Barrier(MPI_Comm_World, ierr)

Procedura ta zakończy działanie jednocześnie na wszystkich procesach w grupie, w momencie kiedy każdy proces dotrze do tego miejsca w kodzie.

W powyższej części artykułu wielokrotnie padało określenie grup procesów. Standardowa grupa, MPI_Comm_World, obejmuje wszystkie procesy. Możemy jednak definiować mniejsze grupy procesów według potrzeb.

Kompilacja

W celu skompilowania programu używającego biblioteki MPI musimy stworzyć specjalny, przystosowany do równoległości program binarny. Służą do tego zazwyczaj nakładki na kompilatory mpif77, mpif90, mpicc oraz mpicxx. Parametry do tych nakładek podajemy dokładnie tak samo, jak do kompilatorów.

Uruchamianie

Uruchamianie programu równoległego następuje zazwyczaj poprzez program mpirun. Najczęściej wywołanie ma postać:

mpirun -np N program.x

gdzie N określa liczbę równoległych procesów.

Dostępność w ICM

Biblioteka MPI jest dostępna na wszystkich klastrach oraz superkomputerach w ICM. Często na pojedynczym klastrze mamy do wyboru kilka implementacji tej biblioteki (np. OpenMPI i MPICH).


  • Hydra
    • mpi/intel/2013_sp1/initial
    • mpi/intel/2013_sp1/latest
    • mpi/intel/2013_sp1/update1
    • mpi/intel/2015/initial
    • mpi/intel/2015/update1
    • mpi/intel/2015/update2
    • mpi/intel/2015/update3
    • mpi/mpich/3.1.3
    • mpi/mpich/3.1.4
    • mpi/mpich/3.1rc2
    • mpi/mpich/3.1rc2/intel
    • mpi/mpich/3.1rc2/intel/2013/update5
    • mpi/mpich/3.1rc2/intel/2013/update5.ndebug
    • mpi/mpich/3.1rc2/intel/2013_sp1/initial
    • mpi/mpich/3.1rc2/intel/2013_sp1/initial.ndebug
    • mpi/mpich/3.1rc2/intel/2013_sp1/update1
    • mpi/mpich/3.1rc2/intel/2013_sp1/update1.ndebug
    • mpi/mpich/3.2a2
    • mpi/mpich/3.2b2
    • mpi/mpich2/1.0.8/intel/2013/update5.ndebug
    • mpi/mpich2/1.2.1/gnu/4.4.7
    • mpi/mpich2/1.2.1/gnu/4.4.7.ndebug
    • mpi/mpich2/1.2.1/intel/2013/update5.ndebug
    • mpi/mpich2/1.4.1p1/gnu/4.4.7.ndebug
    • mpi/mpich2/1.4.1p1/gnu/4.8.1.ndebug
    • mpi/openmpi/1.4.5/pgi/12.10
    • mpi/openmpi/1.6.5/gnu/4.4.7
    • mpi/openmpi/1.6.5/gnu/4.8.1
    • mpi/openmpi/1.6.5/intel/2013/update5
    • mpi/openmpi/1.6.5/intel/2013/update5.mt
    • mpi/openmpi/1.6.5/intel/2013_sp1/initial
    • mpi/openmpi/1.6.5/intel/2013_sp1/update1
    • mpi/openmpi/1.6.5/pgi/13.7
    • mpi/openmpi/1.8.4/gnu/4.8.1
    • mpi/openmpi/1.8.4/intel/2013/update5


Dostępne implementacje

Poza implementacjami związanymi z konkretnymi platformami sprzętowymi, często komercyjnymi, dostępne są trzy popularne, darmowe implementacje MPI: MPICH (http://www-unix.mcs.anl.gov/mpi/mpich/), OpenMPI (http://www.open-mpi.org/) oraz LAM MPI (http://www.lam-mpi.org/). Ich instalacja na własnym komputerze nie powinna nastręczać poważnych trudności (możliwe jest uruchamianie, w celach testowych, programów równoległych na jednym procesorze).

Dalsze informacje