Message Passing Interface

Minimális MPI program

A legegyszerűbb MPI programnak is tartalmaznia kell az MPI_Init és MPI_Finalize hívásokat, így például az alábbi formában képzelhető el:

A program fordítása:

gcc hello.c -o hello -l mpi

A program futtatása:

mpirun hello

Master-slave

Az MPI környezet a programból elérhető teszi, hogy az éppen hogy lett elindítva. A következő példában azt láthatjuk, hogy hogyan tudjuk megállapítani, hogy több folyamat közül melyik-melyik.

#include <mpi/mpi.h>

#include <stdio.h>

int main(int argc, char* argv[])
{
    int rank;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (rank == 0) {
        printf("[%d] I am the master!\n", rank);
    }
    else {
        printf("[%d] I am just a slave.\n", rank);
    }
    MPI_Finalize();
    return 0;
}

A programot fordítani a következő parancs kiadásával tudjuk:

gcc master_slave.c -o master_slave -l mpi

Futtatáshoz adjuk meg, hogy a processzek száma 2 legyen:

mpirun -np 2 master_slave

A program kimenetének az elvártak szerint a következő sorokat kell tartalmaznia:

[0] I am the master!
[1] I am just a slave.

Érdemes azt észrevenni/kipróbálni, hogy futtatástól függően a kiíratások sorrendje változhat.

Blokkoló üzenet küldése

Az üzenet küldésénél a blokkolás azt jelenti, hogy a program csak az üzenet elküldése után folytatja a futását.

#include <mpi/mpi.h>

#include <stdio.h>

int main(int argc, char* argv[])
{
    int rank;
    int message;
    int destination;
    int tag;
    MPI_Status status;
    
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (rank == 0) {
        printf("[%04d] I am the sender!\n", rank);
        message = 42;
        destination = 1;
        tag = 1;
        printf("[%04d] Start sending ...\n", rank);
        MPI_Send(&message, 1, MPI_INT, destination, tag, MPI_COMM_WORLD);
        printf("[%04d] Message has sent!\n", rank);
    }
    else {
        printf("[%04d] I am the receiver!\n", rank);
        printf("[%04d] Wait for a message ...\n", rank);
        MPI_Recv(&message, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        printf("[%04d] The received message is %d.\n", rank, message);
        printf("[%04d] The sender's rank is %d.\n", rank, status.MPI_SOURCE);
    }
    MPI_Finalize();
    return 0;
}

Néhány észrevétel:

  • Az MPI_Send és MPI_Recv függvény visszatérési értéke ebben a rövid példában nincs ellenőrízve. A függvény egy hibakódot ad vissza, amellyel ellenőrízni lehet, hogy sikeres volt-e a küldés, vagy ha nem, akkor miért (http://web.mit.edu/22.00J/www/www3/MPI_Send.html).

  • Futtatásonként a kiírt üzenetek sorrendje eltérhet.

  • A program feltételezi, hogy legalább 2 process van.

  • Szintén feltételezés, hogy a címzett rangja 1.

  • A message változó kétféle módon kerül felhasználásra a küldő és fogadó oldalon.

Átlagolós példa

Tegyük fel, hogy készíteni szeretnénk egy olyan programot, amelyben van egy megkülönböztetett (master) folyamat, amelyik a többi folyamattól vár egy-egy lebegőpontos értékeket, majd ezek átlagát számítja ki a többi által összességében becsült értékként.

Ahhoz, hogy a master folyamat tudja, hogy mikor érkezik be számára az utolsó érték, az egyszerűbb megoldás az, hogy ha kiszámítja a rajta kívül lévő folyamatok számát. Ez az MPI_Comm_Size függvénnyel lekérdezhető.

Az egyszerűség kedvéért most feltételezzük azt, hogy minden folyamat a saját rangját küldi át a master-nek (így az eredmény a rangok átlaga lesz majd).

Blokkoló hívás segítségével a következőképpen oldható meg a probléma:

#include <mpi/mpi.h>

#include <stdio.h>

void send_guess(int rank)
{
    float guess;
    int destination;
    int tag;

    guess = rank;
    destination = 0;
    tag = 1;
    printf("[%04d] Send %f to 0 ...\n", rank, guess);
    MPI_Send(&guess, 1, MPI_FLOAT, destination, tag, MPI_COMM_WORLD);
    printf("[%04d] Message has sent!\n", rank);
}

void collect_guesses(int rank)
{
    int n_tasks;
    int n_received_guesses;
    float guess;
    MPI_Status status;
    float sum;
    
    MPI_Comm_size(MPI_COMM_WORLD, &n_tasks);
    n_received_guesses = 0;
    
    printf("[%04d] I wait %d guesses.\n", rank, n_tasks - 1);
    sum = 0.0;
    while (n_received_guesses < n_tasks - 1) {
        MPI_Recv(&guess, 1, MPI_FLOAT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        printf("[%04d] I received %f from %d.\n", rank, guess, status.MPI_SOURCE);
        sum += guess;
        ++n_received_guesses;
    }
    printf("[%04d] All guesses has received!\n", rank);
    printf("[%04d] The result is %f.\n", rank, sum / n_received_guesses);
}

int main(int argc, char* argv[])
{
    int rank;
    float guess;
    int destination;
    int tag;
    MPI_Status status;
    
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (rank > 0) {
        send_guess(rank);
    }
    else {
        collect_guesses(rank);
    }
    MPI_Finalize();
    return 0;
}

A probléma megoldható az MPI_Reduce függvényének a segítségével is. Ehhez itt található egy részletes leírás: https://mpitutorial.com/tutorials/mpi-reduce-and-allreduce/

Ezzel a következő formában képzelhető el:

#include <mpi/mpi.h>

#include <stdio.h>

void send_guess(int rank)
{
    float guess;
    float result;

    guess = rank;
    result = 0.0;
    printf("[%04d] I guess %f ...\n", rank, guess);
    MPI_Reduce(&guess, &result, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD);
    printf("[%04d] I will return.\n", rank);
}

void collect_guesses(int rank)
{
    int n_tasks;
    float guess;
    float result;
    
    MPI_Comm_size(MPI_COMM_WORLD, &n_tasks);
    printf("[%04d] The number of tasks is %d.\n", rank, n_tasks);
    printf("[%04d] Start reduction ...\n", rank);
    guess = 0.0;
    result = 0.0;
    MPI_Reduce(&guess, &result, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD);
    result /= (n_tasks - 1);
    printf("[%04d] The result is %f.\n", rank, result);
}

int main(int argc, char* argv[])
{
    int rank;
    
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (rank > 0) {
        send_guess(rank);
    }
    else {
        collect_guesses(rank);
    }
    MPI_Finalize();
    return 0;
}

Figyelem

A MPI_Reduce függvény esetében figyelni kell a függvény paramétereinek az inicializálása! A függvény a szinkronizációt ugyan elvégzi, de a kezdeti értékek beállítása esetleges.

A program a következő tömörebb formába is átírható:

#include <mpi/mpi.h>

#include <stdio.h>

int main(int argc, char* argv[])
{
    int rank;
    int n_tasks;
    float guess;
    float result;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    
    if (rank == 0) {
        MPI_Comm_size(MPI_COMM_WORLD, &n_tasks);
        printf("[%04d] The number of tasks is %d.\n", rank, n_tasks);
        guess = 0.0;
    }
    else {
        guess = rank;
        printf("[%04d] I guess %f ...\n", rank, guess);
    }

    result = 0.0;
    printf("[%04d] Start reduction ...\n", rank);
    MPI_Reduce(&guess, &result, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD);

    if (rank == 0) {
        result /= (n_tasks - 1);
        printf("[%04d] The result is %f.\n", rank, result);
    }
    
    MPI_Finalize();
    return 0;
}