1 /* -------------------------------------------------------------------- *
2 * mpi_demo.c *
3 * *
4 * Programme de démonstration MPI permettant d'évaluer une expression. *
5 * -------------------------------------------------------------------- */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <lam/mpi.h> /* définitions MPI */
9 #define MSG_DATA 100 /* message du maître aux esclaves */
10 #define MSG_RESULT 101 /* message des esclaves au maître */
11 #define MASTER 0 /* rank du maître */
12 #define SLAVE_1 1 /* rank du premier esclave */
13 #define SLAVE_2 2 /* rank du second esclave */
14 /* fonctions d'exécution du maître et des deux esclaves */
15 void master(void);
16 void slave_1(void);
17 void slave_2(void);
18 int main(int argc, char** argv)
19
20 int myrank, size;
21
22 /* initialisation de MPI */
23 MPI_Init(&argc, &argv);
24 /* obtention de la taille du communicateur, ou du nombre de processes */
25 MPI_Comm_size(MPI_COMM_WORLD, &size);
26 /* vérification du bon nombre de processes */
27 if(size != 3)
28
29 fprintf(stderr, "Error: Three copies of the program should be run.n");
30 MPI_Finalize();
31 exit(EXIT_FAILURE);
32
33
34 /* obtention du rank du process */
35 MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
36 /* exécution des tâches en fonction du rank */
37 if(myrank == MASTER)
38 master();
39 else if(myrank == SLAVE_1)
40 slave_1();
41 else
42 slave_2();
43 /* sortie de MPI */
44 MPI_Finalize();
45 exit(EXIT_SUCCESS);
46 /* fin de main() */
47 /* fonction exécutant les tâches du maître */
48 void master(void)
49
50 int a, b, c, d;
51 int buf[2];
52 int result1, result2;
53 MPI_Status status;
54 printf("Enter the values of a, b, c, and d: ");
55 scanf("%d %d %d %d", &a, &b, &c, &d);
56 /* envoi de a et b au premier esclave */
57 buf[0] = a;
58 buf[1] = b;
59 MPI_Send(buf, 2, MPI_INT, SLAVE_1, MSG_DATA, MPI_COMM_WORLD);
60 /* envoi de c et d au second esclave */
61 buf[0] = c;
62 buf[1] = d;
63 MPI_Send(buf, 2, MPI_INT, SLAVE_2, MSG_DATA, MPI_COMM_WORLD);
64 /* réception des résultats en provenance des esclaves */
65 MPI_Recv(&result1, 1, MPI_INT, SLAVE_1, MSG_RESULT,
66 MPI_COMM_WORLD, &status);
67 MPI_Recv(&result2, 1, MPI_INT, SLAVE_2, MSG_RESULT,
68 MPI_COMM_WORLD, &status);
69 /* résultat final */
70 printf("Value of (a + b) * (c - d) is %dn", result1 * result2);
71 /* end master() */
72 /* fonction exécutant les tâches du premier esclave*/
73 void slave_1(void)
74
75 int buf[2];
76 int result;
77 MPI_Status status;
78
79 /* réception des valeurs en provenance du maître*/
80 MPI_Recv(buf, 2, MPI_INT, MASTER, MSG_DATA, MPI_COMM_WORLD, &status);
81
82 /* find a + b */
83 result = buf[0] + buf[1];
84 /* envoi des résultats au maître */
85 MPI_Send(&result, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);
86 /* end slave_1() */
87 /* fonction exécutant les tâches du second esclave*/
88 void slave_2(void)
89
90 int buf[2];
91 int result;
92 MPI_Status status;
93
94 /* réception des valeurs en provenance du maître */
95 MPI_Recv(buf, 2, MPI_INT, MASTER, MSG_DATA, MPI_COMM_WORLD, &status);
96
97 /* find c - d */
98 result = buf[0] - buf[1];
99 /* envoi des résultats au maître */
100 MPI_Send(&result, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);
101 /* end slave_2() */
102 /* end mpi_demo.c */
1 # Makefile pour le programme de démonstration MPI - makefile.mpidemo
2 .SILENT:
3 CFLAGS=-I/usr/include/lam -L/usr/lib/lam
4 CC=mpicc
5 mpi_demo : mpi_demo.c
6 $(CC) $(CFLAGS) mpi_demo.c -o mpi_demo
Pour compiler ce programme, passez la commande make f makefile.mpidemo. Une fois le code compilé, il vous faudra démarrer lam pour pouvoir l'exécuter, ce qui va être fait avec la commande lamboot. Il n'y aura plus alors qu'à passer la commande mpirun np 3 mpi_demo.
[rahul@joshicomp parallel]$ lamboot
LAM 6.3.1/MPI 2 C++/ROMIO - University of Notre Dame
[rahul@joshicomp parallel]$ mpirun -np 3 mpi_demo
Enter the values of a, b, c, and d: 1 2 3 4
Value of (a + b) * (c - d) is -3
[rahul@joshicomp parallel]$
4.2 Explication du programme
Pour utiliser l'environnement MPI et ses fonctions, vous devez déclarer le fichier d'inclusion mpi.h dans vos sources, comme il est fait ligne 8.
Dans le cas de PVM, les divers processes sont identifiés avec leurs ID, tandis que sous MPI, chaque process se voit assigné un entier, unique, nommé rank. La première valeur de rank est 0, et cette valeur, propre à chaque process. Elle est ensuite utilisée pour identifier le process et communiquer avec.
Ensuite, chaque process est membre d'un groupe de communication. Un groupe de communication réunit l'ensemble des processes ayant la capacité de communiquer entre eux. Par défaut, tous les processes sont membres du groupe MPI_COMM_WORLD. La création de nouveaux groupes de communication augmentant significativement la complexité d'un programme, nous nous limiterons dans notre exemple à l'utilisation du groupe par défaut.
Tout programme MPI doit d'abord appeler la fonction MPI_Init(), qui est à la base de l'enregistrement du process dans le système MPI. Ensuite, il nous faut déterminer la taille du communicateur, c'est-à-dire trouver le nombre de processes utilisant la fonction MPI_Comm_size(). Le premier paramètre de cette fonction est le communicateur, et le second est un pointeur vers un entier dans lequel la taille sera retournée. Dans notre cas, nous avons besoin de 3 processes, un maître et deux esclaves.
Ensuite, nous obtenons le rank en appelant MPI_Comm_rank(). Les trois processes auront comme ranks 0, 1, et 2, respectivement. Tous les processes sont sur un pied d'égalité, c'est-à-dire qu'il n'existe pas de relation maître/esclave entre eux. Nous choisissons le process de rank 0 comme étant le maître, et les processes de rank 1 et 2 comme esclaves. Nous pouvons également voir que le programme contient le code du maître et des deux esclaves, et nous choisissons quelle partie exécuter en fonction du rank.
Remarquez qu'il n'y a pas de filiation de processes comme avec PVM, et comme nous allons le voir, nous décidons de choisir le nombre de processes à générer en fonction d'un argument passé sur la ligne de commande.
Une fois cette opération achevée, nous allons appeler MPI_Finalize() pour effectuer une sortie dans les règles. Abordons maintenant le cas du maître. Après saisie des valeurs a, b, c, d par l'utilisateur, il faut envoyer a et b vers l'esclave 1 et c et d vers l'esclave 2. Au lieu d'envoyer les valeurs séparément, nous allons plutôt les placer dans un tableau que nous enverrons à chaque esclave. Il est préférable de procéder ainsi, de façon à limiter le plus possible les échanges de messages, afin d'obtenir les meilleures performances.
Lorsque le tableau est rempli, nous n'avons pas à nous préoccuper de l'encodage, car, contrairement à PVM, ces opérations sont réalisées de façon masquée. Nous pouvons donc appeler directement MPI_Send() pour envoyer les données. En ligne 59, le premier paramètre est l'adresse du buffer, le second le nombre d'éléments contenus dans le message, et le troisième la spécification du type de donnée contenue dans le buffer (MPI_INT dans ce cas). Viennent ensuite le rank du process auquel nous voulons que le message soit envoyé, puis l'étiquette associée au message, similaire dans ce cas à l'étiquette PVM. Enfin, le dernier paramètre est le groupe de communication dont le récepteur est membre, MPI_COMM_WORLD dans ce cas.
Une fois les données distribuées aux esclaves, le maître doit attendre le retour des résultats. Pour plus de simplicité, nous recevons le message de l'esclave 1, puis celui de l'esclave 2. Pour ce faire, nous utilisons la fonction MPI_Recv(). Comme précédemment, l'encodage est effectué de façon transparente. Le premier argument, en ligne 65, est l'adresse du buffer dans lequel les données vont être récupérées, le second argument est le nombre d'éléments contenus dans le buffer, 1 dans notre cas. Ensuite, le type de donnée (MPI_INT). Les trois paramètres qui suivent caractérisent le rank de la source du message, l'étiquette du message attendu et le communicateur dont l'émetteur est membre. Le dernier argument est un pointeur vers une structure de type MPI_Status dans lequel des informations d'état vont être retournées.
Dans ce programme, le code du maître et celui des esclaves est contenu dans le même exécutable. Nous verrons ultérieurement comment lancer de multiples programmes. Dans le makefile, nous voyons que la compilation est assurée par un sur-programme, mpicc, qui assure automatiquement l'édition de liens avec les bibliothèques appropriées.
4.3 Le programme d'addition, à nouveau
Nous allons ré-effectuer l'implémentation du programme d'addition que nous avons conçu avant d'aborder MPI. Nous allons voir ici comment exécuter des programmes distincts avec MPI. Lorsqu'un seul exécutable est utilisé, l'on se trouve en mode SPMD (Single Program Multiple Data), et en mode MPMD (Multiple Program Multiple Data) dans le cas où plusieurs exécutables différents sont utilisés. Le mode MPMP requiert un protocole d'exécution, mais avant cela, découvrons les sources des programmes maître et esclave.
1 /* -------------------------------------------------------------------- *
2 * master_mpi.c *
3 * *
4 * Programme maître pour l'addition des éléments d'un tableau avec MPI *
5 * -------------------------------------------------------------------- */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <lam/mpi.h> /* constants et fonctions MPI */
9 #include "tags.h" /* étiquettes des messages */
10 #include "common.h" /* constantes communes */
11 int main(int argc, char** argv)
12
13 int size, i, sum;
14 int items[SIZE];
15 int results[NUM_SLAVES];
16 MPI_Status status;
17 /* initialisation de MPI */
18 MPI_Init(&argc, &argv);
19 /* vérification du bon nombre de processes */
20 MPI_Comm_size(MPI_COMM_WORLD, &size);
21 if(size != 5)
22
23 fprintf(stderr, "Error: Need exactly five processes.n");
24 MPI_Finalize();
25 exit(EXIT_FAILURE);
26
27 /* initialize the `items' array */
28 for(i = 0; i < SIZE; i++)
29 items[i] = i;
30 /* distribution des données aux esclaves */
31 for(i = 0; i < NUM_SLAVES; i++)
32 MPI_Send(items + i*DATA_SIZE, DATA_SIZE, MPI_INT, i + 1,
33 MSG_DATA, MPI_COMM_WORLD);
34 /* récupération des données en provenance des esclave */
35 for(i = 0; i < NUM_SLAVES; i++)
36
37 int result;
38
39 MPI_Recv(&result, 1, MPI_INT, MPI_ANY_SOURCE, MSG_RESULT,
40 MPI_COMM_WORLD, &status);
41 results[status.MPI_SOURCE - 1] = result;
42
43 /* obentention du résultat final */
44 sum = 0;
45 for(i = 0; i < NUM_SLAVES; i++)
46 sum = sum + results[i];
47 printf("The sum is %dn", sum);
48 /* sortie de MPI */
49 MPI_Finalize();
50 exit(EXIT_SUCCESS);
51 /* fin de main() */
52 /* fin de master_mpi.c */
1 /* -------------------------------------------------------------------- *
2 * slave_mpi.c *
3 * *
4 * Programme esclave pour l'addition des éléments d'un tableau avec MPI *
5 * -------------------------------------------------------------------- */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <lam/mpi.h> /* constantes et fonctions MPI */
9 #include "tags.h" /* étiquettes des messages */
10 #include "common.h" /* constantes communes */
11 #define MASTER 0 /* rank du maître */
12 int main(int argc, char** argv)
13
14 int items[DATA_SIZE];
15 int size, sum, i;
16 MPI_Status status;
17 /* initialisation de MPI */
18 MPI_Init(&argc, &argv);
19 /* vérification du bon nombre de processes */
20 MPI_Comm_size(MPI_COMM_WORLD, &size);
21 if(size != 5)
22
23 fprintf(stderr, "Error: Need exactly five processes.n");
24 MPI_Finalize();
25 exit(EXIT_FAILURE);
26
27 /* réception des données en provenance du maître */
28 MPI_Recv(items, DATA_SIZE, MPI_INT, MASTER, MSG_DATA,
29 MPI_COMM_WORLD, &status);
30 /* calcul de la somme */
31 sum = 0;
32 for(i = 0; i < DATA_SIZE; i++)
33 sum = sum + items[i];
34 /* envoi des résultats au maître */
35 MPI_Send(&sum, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);
36 /* sorie de MPI */
37 MPI_Finalize();
38 exit(EXIT_SUCCESS);
39 /* fin de main() */
40 /* fin de slave_mpi.c */
1 # Makefile du programme d'addition MPI - makefile.mpiadd
2 .SILENT:
3 CFLAGS=-I/usr/include/lam -L/usr/lib/lam
4 CC=mpicc
5 all : master_mpi slave_mpi
6 master_mpi : master_mpi.c common.h tags.h
7 $(CC) $(CFLAGS) master_mpi.c -o master_mpi
8 slave_mpi : slave_mpi.c common.h tags.h
9 $(CC) $(CFLAGS) slave_mpi.c -o slave_mpi
Pour compiler, il suffit de passer la commande make f makefile.mpiadd, ce qui aura pour résultat la création des programmes master_mpi et slave_mpi. Il nous faut maintenant décrire à MPI comment exécuter ces programmes, et c'est là qu'intervient le protocole d'exécution. Ce protocole est décrit dans un fichier, qui spécifie les exécutables et les n
uds sur lesquels ces exécutables doivent être lancés. Créez un fichier add.schema avec le contenu suivant:
# protocole d'exécution pour le code d'addition sous MPI
n0 master_mpi
n0 -np 4 slave_mpi
Ce fichier décrit comment MPI doit lancer un exemplaire du maître (lequel aura le rank 0) et quatre exemplaires des esclaves, l'ensemble sur le n
ud n0 (le n
ud local). De nombreux autres paramètres peuvent être spécifiés, et vous trouverez une description complète dans les manpages de appschema(1).
Dès que le fichier est créé, vous pouvez lancer le programme comme suit:
[rahul@joshicomp parallel]$ lamboot
LAM 6.3.1/MPI 2 C++/ROMIO - University of Notre Dame
[rahul@joshicomp parallel]$ mpirun add.schema
The sum is 4950
[rahul@joshicomp parallel]$
L'ensemble du programme doit être assez simple à comprendre. A la ligne 39, lors de la réception des résultats intermédiaires en provenance des esclaves, nous définissons la source comme MPI_ANY_SOURCE, voulant avoir la possibilité de répondre aux esclaves dès qu'ils ont fini leur exécution. Dans ce cas, la structure status contient l'identifiant de l'émetteur dans le champ MPI_SOURCE, que nous utilisons pour savoir où stocker le résultat reçu.
Dans le cas où l'on a à disposition un réseau de machines, il est possible de faire tourner les programmes sur plusieurs n
uds en modifiant le fichier descripteur du protocole d'exécution en conséquence. Au lieu de définir n0 comme unique n
ud, définissez le nom de n
ud et le nombre de processes que vous voulez lancer.
5. Conclusion
Nous avons vu comment écrire des programmes parallèles utilisant les bibliothèques PVM et MPI. Ces bibliothèques étant disponibles sur de nombreuses plates-formes, et étant des standards de facto, les programmes développés sur ces bases tourneront avec peu ou même pas de modifications sur de grandes configurations si le besoin survient.
Nous nous sommes focalisés dans cet article sur les fonctions de communication point à point proposées par ces bibliothèques et leur utilisation dans le passage de messages. A côté de ces fonctions, PVM, tout comme MPI, offre beaucoup de fonctions évoluées, comme des fonctions de communication globale (broadcast ou multicast), de création et de gestion de groupes de processes, des fonctions de réduction, etc. Vous êtes invités à découvrir ces fonctions avancées. Ces produits en domaine public nous permettent d'utiliser un réseau de machines comme un seul grand système. Par conséquent, si vous avez un problème complexe à résoudre, vous pouvez envisager d'utiliser le réseau à votre disposition. Vous pourrez vous référer aux ouvrages listés ci-dessous pour plus d'informations, ainsi qu'à de nombreux autres ouvrages également existants.
1.
PVM : Parallel Virtual Machine - A User's Guide and Tutorial for Networked Parallel Computing. Al Geist, Adam Beguelin, Jack Dongarra, Robert Manchek, Weicheng Jiang et Vaidy Sunderam, MIT Press. Disponible sur
hyperlink
2.
MPI : The Complete Reference. Marc Snir, Steve Otto, Steven Huss-Lederman, David Waker et Jack Dongarra, MIT Press. Disponible sur
hyperlink.
3.
RS/6000 SP : Practical MPI Programming. Yukiya Aoyama et Jan Nakano, International Technical Support Organization, IBM Corporation,
hyperlink.
4.
A Beginner's Guide to PVM Parallel Virtual Machine. Clay Breshears et Asim YarKhan, Joint Institute of Computational Science, University of Tennessee, USA.
hyperlink.
5.
PVM : An Introduction to Parallel Virtual Machine. Emily Angerer Crawford, Office of Information Technology, High Performance Computing,
hyperlink.
6. Remerciements
Je voudrais remercier mon chef de travaux, le Dr. Uday Khedker pour son aide et ses encouragements. Je voudrais également remercier le Center for Developement of Advanced Computing pour m'avoir autorisé à exécuter les codes MPI et PVM sur le supercalculateur PARAM, et le Dr. Anabarsu pour m'avoir guidé lors du développement.
Copyright © 2001, Rahul U. Joshi.
Copying license hyperlink
Published in Issue 65 of Linux Gazette, April 2001