MPI-IO
Wprowadzenie¶
MPI-IO to część standardu MPI definiująca API do równoległej pracy z plikami umieszczonymi we współdzielonym, sieciowym systemie plików przez wiele rozproszonych procesów. Przewidziany szczególnie do wydajnej pracy z równoległymi systemami plików, takimi jak Lustre czy GPFS.
MPI-IO jest traktowane jako niskopoziomowe API do równoległego I/O, chociaż nadaje się również do bezpośredniego użycia. Wyżej poziomowe biblioteki jak np. HDF5 czy NetCDF, w zakresie pracy z plikami rozproszonymi często bazują właśnie na MPI-IO (patrz parallel-HDF5).
W standardzie MPI obecny od wersji MPI-2 (patrz MPI > Informacje o standardzie), na przestrzeni lat interfejs przechodzi tylko nieznaczne zmiany. Z istotnych, w MPI-3 dodano funkcje iread
/iwrite
dla operacji kolektywnych, które powinny być używane w miejsce dotychczasowych split collective tj. funkcji z sufiksami _begin
oraz _end
.
Dostępność¶
Jako część standardu, MPI-IO jest dostarczane w ramach implementacji biblioteki MPI (patrz MPI > Implementacje). Standard określa interfejs programistyczny dla języków C / C++ / Fortran. Implementacja mpi4py dla języka Python również udostępnia funkcje MPI-IO.
Ważną implementacją MPI-IO jest ROMIO, które obecnie jest rozwijane w ramach MPICH. Wiele implementacji MPI w części dotyczącej I/O również opiera się o ROMIO, dlatego pewien zestaw optymalizacji (wskazówek) związanych z I/O jest przenośny pomiędzy różnymi bibliotekami MPI.
Szczegóły¶
Podstawowe funkcjonalności¶
Podstawowe funkcjonalności dostarczane przez MPI-IO rozwijające standardowe metody pracy z plikami udostępniane przez system operacyjny, to:
- synchronizacja wielu procesów przy pracy ze współdzielonymi plikami,
- możliwość równoległego odczytu/zapisu danych z/do pliku przez wiele procesów,
- możliwość odczytywania/zapisywania nieciągłego układu danych w pliku.
Biblioteka udostępnia programiście standardowy logiczny widok pliku (ciągły strumień danych). Dane w pliku są zapisywane w postaci binarnej, przy czym możliwe jest kilka reprezentacji danych (konwersji między reprezentacją danych w pamięci programu, a reprezentacją na dysku twardym). Szczegóły wykonania fizycznego odczytu/zapisu są zależne od implementacji biblioteki.
Biblioteka daje możliwość przeplatania danych z różnych procesów, poprzez definiowanie widoku na plik. Dzięki widokowi każdy proces może odczytywać/zapisywać podzbiór danych według zadanego schematu danych. Definiowanie schematu jest oparte o złożone typy danych (MPI Derived Datatypes).
Sposoby dostępu¶
Współdzielona praca nad plikiem opiera się o kolektywne otwarcie pliku przez grupę procesów (wszystkie procesy muszą wywołać funkcję aby doszło do jej wykonania).
Dostęp (odczyt/zapis) do danych jest możliwy przez:
- bezpośrednią adresację, tj. podanie pozycji względem początku pliku (offset),
- indywidualny wskaźnik danego procesu (automatycznie przesuwany),
- współdzielony wskaźnik grupy procesów (automatycznie przesuwany i synchronizowany).
Dostęp jest możliwy zarówno w sposób indywidualny jak i grupowy (funkcje read
oraz write
posiadają swoje kolektywny odpowiedniki z sufiksem _al
).
Standardowy dostęp do danych jest blokujący (funkcja zwraca sterowanie dopiero po wykonaniu operacji I/O), ale biblioteka umożliwia również dostęp nieblokujący według schematu:
- szybkie zainicjowanie dostępu do danych,
- powrót do obliczeń,
- zaczekanie aż dane będą dostępne.
Taki sposób stwarza okazję do asynchronicznego odczytu/zapisu danych. Należy mieć na uwadze, że prawdziwa asynchroniczność zależy od implementacji biblioteki, standard jej nie wymusza (np. cała praca z odbiorem danych może zostać wykonana dopiero w momencie zaczekania na zakończenie operacji).
Optymalizacja równoległego dostępu¶
Typową metodą optymalizacji równoległego dostępu do pliku jest tzw. kolektywne buforowanie (zwane również “dwufazowym I/O”), w którym występuje faza komunikacji i wymiany danych między procesami, oraz faza bezpośredniego dostępu do systemu plików.
Na przykładzie zapisu, najpierw dane są wymieniane między procesami przez sieć i zostają zgrupowane w buforach tylko kilku procesów (agregatorzy). Dane są grupowane w taki sposób, by umożliwić sekwencyjny zapis dłuższych fragmentów do pliku. Następnie tylko te kilka procesów wykonuje operacje zapisu. Dzięki takiemu podejściu biblioteka MPI porządkuje sposób dostępu i interakcja z systemem plików zostaje zoptymalizowana.
Na sposób fizycznego zapisu można wpływać (optymalizować) poprzez wskazówki dla biblioteki, które można przekazać na dwa sposoby:
- w kodzie aplikacji, definiując odpowiednie wartości w obiektach
MPI_Info
(podawanych przy otwieraniu pliku), - w momencie uruchomienia programu, eksportując wartości odpowiednich zmiennych środowiskowych.
Typowo wskazówki służą do:
- ustawienia parametrów pliku w równoległym systemie plików (np.
striping_unit
,striping_factor
dla systemów plików typu Lustre) - optymalizacji metod zbierania danych z wielu procesów (np. rozmiary i ilość buforów do agregacji danych)
Kwestia sposobu wykorzystania wskazówek jest zależna od implementacji biblioteki. Każda biblioteka definiuje swój zestaw wskazówek i konfigurowalnych zmiennych środowiskowych.
Linki¶
- dokumentacja API
- standard MPI (rozdział 14 “I/O” w wersji 4.0, czerwiec 2021)
- API na stronie Open MPI (funkcje z prefiksem
MPI_File_
) - mpi4py dla języka Python
- implementacja ROMIO
- materiały
- podstawy MPI-I/O
- zaawansowany kurs MPI (fragmenty dotyczą MPI-I/O)