MPICH
Ten artykuł należy dopracować |
MPICH – ogólnodostępna, darmowa i przenośna implementacja standardu MPI. Pozwala na przekazywanie komunikatów pomiędzy aplikacjami działającymi równolegle. Nadaje się do stosowania na małych klastrach.
Aktualna wersja stabilna | 3.3.2 (13 listopada 2019) [±] |
---|---|
System operacyjny | Microsoft Windows, Unix |
Rodzaj | Biblioteka zgodna ze standardem MPI |
Strona internetowa |
Najnowszą wersję biblioteki MPICH można pobrać ze strony domowej projektu. Biblioteki MPICH można używać zarówno na systemach klasy MS Windows jak i Unix.
Rozwinięciem MPICH jest MPICH2.
Powstała także wersja tej biblioteki o nazwie MPICH-G2 pozwalająca uruchamiać aplikacje równoległe w środowiskach gridowych, z wykorzystaniem pakietu Globus Toolkit jako warstwy pośredniej. Dzięki temu rozwiązaniu aplikacja może działać na kilku klastrach rozproszonych geograficznie.
CharakterystykaEdytuj
MPICH jest implementacją biblioteki sterowania procesem obliczeń równoległych na maszyny klasy PC. Protokół ten przeznaczony jest do sterowania procesem obliczeń równoległych w sieciach rozproszonych. Biblioteka procedur MPICH jest dostępna bezpłatnie. Najnowsza wersja tej biblioteki MPICH2 poza zapewnieniem bardziej wydajnych mechanizmów komunikacji posiada dodatkowo:
- wsparcie dla komunikacji jednostronnej
- rozszerzoną funkcjonalność MPI-IO
InstalacjaEdytuj
Opis instalacji MPICH (jednej z implementacji MPI)
1. Na początku należy pobrać źródła programu : mpich.tar.gz
1. Na początku należy pobrać źródła programu : mpich.tar.gz
2. Rozpakowujemy plik mpich.tar.gz
# tar xfz mpich.tar.gz
3. Będąc w katalogu /mpich wydajemy komendę:
# ./configure --prefix=/opt/mpich
--prefix=/opt/mpich - określa ścieżkę docelową dla MPICH
4. Kompilujemy program wydając komendę:
# make
5. Dokonujemy instalacji w katalogu podanym w opcji prefix:
# make install
KonfiguracjaEdytuj
Konfiguracja MPICH
W pliku machines.LINUX (katalog: /opt/mpich/share lub inny wybrany w opcji prefix podczas instalacji) dopisujemy nazwy hostów wchodzących w skład klastra:
W pliku machines.LINUX (katalog: /opt/mpich/share lub inny wybrany w opcji prefix podczas instalacji) dopisujemy nazwy hostów wchodzących w skład klastra:
hostname1:liczba_procesów
hostname2:liczba_procesów
hostname3:liczba_procesów
gdzie:
- hostname – oznacza nazwa komputera wchodzącego w skład klastra,
- liczba_procesów – liczba procesorów danego hosta
Przesyłanie komunikatówEdytuj
Komunikacja punkt – punktEdytuj
PrzykładEdytuj
Przykład komunikacji międzyprocesowej typu punkt-punkt. Zadaniem programu jest przesłanie komunikatu o treści "Komunikat od procesu 0" z procesu o numerze rank = 0 (SOURCE) do procesu o numerze rank = 1 (DEST).
#include <stdio.h> #include "mpi.h"
#define TAG 0 #define COUNT 25 #define SOURCE 0 #define DEST 1
int main(int argc, char *argv[]) { int rank, size; MPI_Status status;
char Msg[]="Komunikat od procesu 0"; // treść komunikatu przesyłanego między procesami char Recv[COUNT]; MPI_Init(&argc,&argv); // inicjalizacja środowiska MPI manual MPI_Comm_size(MPI_COMM_WORLD,&size); // liczba procesów w grupie zapisywana jest w zmiennej size manual MPI_Comm_rank(MPI_COMM_WORLD,&rank); // rank bieżącego procesu zapisywany jest w zmiennej rank manual
if (size > 1) { // sprawdzane jest czy istnieje minimalna liczba procesów
// Komunikat przesyłany jest od procesu o numerze rank = 0 (SOURCE) // do procesu o numerze rank = 1 (DEST) if (rank == DEST) // jeżeli procesem jest proces odbierający komunikat: { printf ("process %d of %d waiting for message from %d\n", rank, size, SOURCE); // odbiór komunkatu i zapis zawartości do bufora Recv manual MPI_Recv(Recv, COUNT, MPI_CHAR, SOURCE, TAG, MPI_COMM_WORLD, &status); printf ("process %d of %d has received: '%s'\n", rank, size, Recv); }
else
if (rank == SOURCE) // jeżeli procesem jest proces nadający komunikat: { printf ("process %d of %d sending '%s' to %d\n", rank, size, Msg, DEST); // wysyłanie komunkatu zawartego w buforze Msg manual MPI_Send(Msg, COUNT, MPI_CHAR, DEST, TAG, MPI_COMM_WORLD); } } MPI_Finalize(); // zakończenie wykonywania procesów MPI manual return 0; }
Program kompiluje się wprowadzając polecenie:
# mpicc -o program_mpi program_mpi.c
Komunikacja grupowaEdytuj
MPICH dostarcza kilku funkcji, ułatwiających komunikowanie się jednocześnie ze wszystkimi procesami w grupie. Poza sporym ułatwieniem zapewniają one (a raczej zapewnia to MPICH), że dane zostaną przesłane w optymalny sposób (naiwny broadcast(iteracja) vs. MPI_Bcast(algorytm „drzewiasty”) ).
MPI_BcastEdytuj
Rozsyła komunikat do wszystkich procesów w grupie.
int vsize = 4; float vect[vsize]; ... int status = MPI_Bcast( vect ,vsize ,MPI_FLOAT ,0 /*root*/ , MPI_COMM_WORLD); // man
MPI_ScatterEdytuj
Funkcja dzieli wektor danych wejściowych (w procesie oznaczonym jako root) i rozsyła je tak, że procesowi o randze N jest przesyłana N-ta część wektora danych.
int vsize = 4; float matrix[vsize*vsize]; float rvect[vsize]; int status = MPI_Scatter(matrix, vsize, MPI_FLOAT, rvect, vsize, MPI_FLOAT, 0 /*root*/, MPI_COMM_WORLD); // do 4-ech procesów!!! man
MPI_GatherEdytuj
Funkcja odwrotna do MPI_Scatter. Zbiera dane w procesie oznaczonym root. Ustawia je w kolejkę zgodnie z rangą nadsyłającego procesu.
int vsize = 4; float matrix[vsize*vsize]; float rvect[vsize]; int status = MPI_Gather(rvect, vsize, MPI_FLOAT, matrix, vsize, MPI_FLOAT, 0 /*root*/ ,MPI_COMM_WORLD); // od 4-ech procesów !!!man
MPI_ReduceEdytuj
Funkcja wykonuje zdefiniowaną operację na wysłanych przez procesy danych. Możliwe operacje:
- MPI_MAX – maximum
- MPI_MIN – minimum
- MPI_SUM – suma
- MPI_PROD – produkt
- MPI_LAND – logiczny AND
- MPI_LOR – logiczny OR
- MPI_LXOR – logiczny XOR
- MPI_BAND – bitowy AND
- MPI_BOR – bitowy OR
- MPI_BXOR – bitowy XOR
int val = 1, procCnt; int status = MPI_Reduce( &val ,&procCnt ,1 ,MPI_INT ,MPI_SUM ,0 /*root*/ ,MPI_COMM_WORLD ); // alternatywna metoda liczenia procesów :) man
PrzykładEdytuj
Przykład komunikacji grupowej realizowanej za pośrednictwem funkcji MPI_Scatter i MPI_Reduce.
Działanie programu polega na rozesłaniu poszczególnych wierszy macierzy zawartej w buforze sendbuf do czterech procesów w grupie.
#include "mpi.h" #include <stdio.h> #define SIZE 4
main(int argc, char *argv[]) { int numtasks, rank, sendcount, recvcount, source; float sendbuf[SIZE][SIZE] = { {1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0}, {9.0, 10.0, 11.0, 12.0}, {13.0, 14.0, 15.0, 16.0} }; // inicjalizacja bufora do wysyłki float recvbuf[SIZE];
MPI_Init(&argc,&argv); // inicjalizacja środowiska MPI manual MPI_Comm_rank(MPI_COMM_WORLD, &rank); // rank bieżącego procesu zapisywany jest w zmienej rank manual MPI_Comm_size(MPI_COMM_WORLD, &numtasks); // liczba procesów w grupie zapisywana jest w zmiennej size manual
if (numtasks == SIZE) { // jeżeli w grupie są dokładnie 4 procesy: source = 1; sendcount = SIZE; recvcount = SIZE;
// rozsyłanie do procesów w grupie komunikatu zawartego w buforze sendbuf manual MPI_Scatter(sendbuf,sendcount,MPI_FLOAT,recvbuf,recvcount, MPI_FLOAT,source,MPI_COMM_WORLD);
printf("rank= %d Results: %f %f %f %f\n",rank,recvbuf[0], recvbuf[1],recvbuf[2],recvbuf[3]); int val = 1, procCnt; MPI_Reduce( &val ,&procCnt ,1 ,MPI_INT ,MPI_SUM ,source ,MPI_COMM_WORLD ); // suma ,czyli liczba procesów man } else printf("Must specify %d processors. Terminating.\n",SIZE);
MPI_Finalize(); // zakończenie wykonywania procesów MPI manual }
Program kompiluje się wprowadzając polecenie:
# mpicc -o program_mpi program_mpi.c
Typy danychEdytuj
Podstawowe typy danychEdytuj
MPI | C | Rozmiar |
---|---|---|
MPI_CHAR | signed char | 1 bajt |
MPI_SHORT | signed short int | 2 bajty |
MPI_INT | signed int | 2 bajty |
MPI_LONG | signed long int | 4 bajty |
MPI_UNSIGNED_CHAR | unsigned char | 1 bajt |
MPI_UNSIGNED_SHORT | unsigned short int | 1 bajt |
MPI_UNSIGNED | unsigned int | 2 bajty |
MPI_UNSIGNED_LONG | unsigned long int | 4 bajty |
MPI_FLOAT | float | 4 bajty |
MPI_DOUBLE | double | 8 bajtów |
MPI_LONG_DOUBLE | long double | 8, 10 lub 12 bajtów |
MPI_BYTE | 8 binarnych cyfr (8 bitów) | 1 bajt |
MPI_PACKED | dane spakowane lub rozpakowane z użyciem MPI_Pack()/ MPI_Unpack() |
W MPI możemy tworzyć własne typy danych. Najczęściej chcemy, aby typ przesyłanych danych był różny od standardowych typów zdefiniowanych w bibliotece MPI. W MPI zdecydowano się na rozwiązanie, w którym programista ma możliwość tworzenia nowych typów danych w czasie wykonywania programu. Takie typy danych nazywane są POCHODNYMI TYPAMI DANYCH lub TYPAMI UŻYTKOWNIKA. Pochodne typy danych tworzymy z podstawowych typów danych.
Typy pochodneEdytuj
Każdy typ danych w MPI jest określony przez tablice typemap podającą dla każdego elementu parami typ podstawowy i przesunięcie w bajtach
Typemap={(type0 ,disp0 ),...,(typen-1 ,dispn-1 )}
przykład: MPI_INT (int,0)
Definicje Dolnej i Górnej Granicy oraz Rozpiętości i Rozmiaru Typów Danych
lb(Typemap) = minj (dispj) ub(Typemap) = maxj (dispj+sizeof(typej))+pad extent(Typemap) = ub(Typemap)-lb(Typemap)
„pad” określa poprawkę ze względu na rozmieszczenie danych w pamięci komputera, w większości przypadków wymagane jest aby dane określonego typu były umieszczone pod adresami będącymi wielokrotnością rozmiaru danego typu, np. int zajmuje 4 bajty więc adres powinien być podzielny przez 4, więc dla:
{(int,0),(char,4)} lb=min(0,4)=0 ub=max(0+4,4+1)+pad=5+pad=8 extent=5+pad=8 rozmiar=5
Pochodne typy danychEdytuj
Contiguous
Jest to najprostszy typ pochodny; elementy są kopiami danego typu wejściowego a ich pozycje są wielokrotnościami jego rozpiętości.
int MPI_Type_contiguous(int count, MPI_Datatype oldtype,MPI_Datatype *newtype);
Vector
Elementy są kopiami danego typu wejściowego, ale pomiędzy nimi występują odstępy będące wielokrotnościami rozpiętości tego typu.
int MPI_Type_vector(int count, int block_length,int stride, MPI_Datatype element_type,MPI_Datatype *newtype);
Hvector
Jak Vector ale odstępy między elementami są określone w bajtach.
int MPI_Type_vector(int count, int block_length,int stride, MPI_Datatype element_type,MPI_Datatype *newtype);
Indexed
Jak Vector ale odstępy między elementami są określone dowolnie przez tablicę przesunięć.
int MPI_Type_indexed(int count,int *array_of_block_lengths,int *array_of_displacements,MPI_Datatype element_type, MPI_Datatype *newtype);
Hindexed
Jak Indexed ale elementy tablicy przesunięć są podane w bajtach.
int MPI_Type_indexed(int count,int *array_of_block_lengths,int *array_of_displacements,MPI_Datatype element_type,MPI_Datatype *newtype);
Struct
Najbardzięj ogólny typ, elementy nie muszą być jednakowego typu, a odstępy między nimi są określone przez tablicę przesunięć, której elementy są podane w bajtach.
int MPI_Type_struct(int count,int *array_of_block_lengths,MPI_Aint *array_of_displacements,MPI_Datatype *array_of_types,MPI_Datatype *newtype);
Extent
Zwraca rozmiar w bajtach danego typu danych. Funkcja ta jest użyteczna w podprogramach, które wymagają rozmiaru danego typu w bajtach np. w celu obliczenia przesunięcia.
int MPI_Type_extent(MPI_Datatype dtype, MPI_Aint *pextent)
Commit
Zatwierdza nowy typ w systemie. Wymagane dla wszystkich nowo stworzonych pochodnych typów danych.
int MPI_Type_commit ( MPI_Datatype *datatype )
Przykład – CONTIGUOUSEdytuj
#include "mpi.h"
#include <stdio.h>
#define SIZE 4
main(int argc, char *argv[]) { int numtasks, rank, source=0, dest, tag=1, i;
float a[SIZE][SIZE] ={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
float b[SIZE];
MPI_Status stat;
MPI_Datatype rowtype;
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Type_contiguous(SIZE, MPI_FLOAT, &rowtype);
MPI_Type_commit(&rowtype);
if (numtasks == SIZE){
if (rank == 0) { for (i=0; i<numtasks; i++) MPI_Send(&a[i][0], 1, rowtype, i, tag, MPI_COMM_WORLD); }
MPI_Recv(b, SIZE, MPI_FLOAT, source, tag, MPI_COMM_WORLD, &stat); printf("rank= %d b= %3.1f %3.1f %3.1f %3.1f\n", rank,b[0],b[1],b[2],b[3]); }
else
printf("Must specify %d processors. Terminating.\n",SIZE);
MPI_Finalize();
}
źródło: https://web.archive.org/web/20090131212047/http://icis.pcz.pl/~roman/mpi-www/