Previous Next Contents

Chapter 2    Réseau de machines sous PVM

2.1  Introduction

PVM veut dire ``Parallel Virtual Machine'': Elle peut fonctionner sur les architectures suivantes:
Stations OS Super-calculateurs
DEC BSD Convex C2, Exemplar
IBM System-5 Cray C90, T-3D
HP Linux Intel Paragon, Hypercube
Silicon Graphics   IBM SP-2, 3090
Sun   TMC CM-2, CM-5
NeXT   Kendall Square
Data General   Sequent
386/486/586 Unix   Maspar
    Encore
    Fujitsu
    Stardent
... ... ...

La machine PVM (voir figure 2.1) est une collection de machines réelles sur un même réseau sur lesquelles s'exécute un démon pvmd3. pvmd3 est un processus UNIX qui contrôle les processus utilisateurs au sein d'une application PVM et qui coordonne les communications entre les machines qui composent la machine PVM. Un démon pvmd3 est présent par ordinateur et par utilisateur de la machine PVM et maintient la configuration globale et locale de la machine PVM.

La librairie PVM permet de communiquer avec ces démons pvmd3 pour créer et détruire des processus, gérer des tampons de communication, envoyer, recevoir (point à point ou par broadcast) des messages (de façon bloquante ou non-bloquante), synchroniser des processus (par barrières de synchronisation) connaître l'état de la configuration de la machine PVM et pour la modifier (dynamiquement)...



Figure 2.1: La machine PVM

2.2  Démarrer par l'exemple

Supposons que nous voulions calculer p en parallèle par la formule suivante,

p =
ó
õ
1


0
4

1+x2
dx
   
n
S
i=1
1

n
4

1+ æ
ç
ç
è
(i-
1

2
)
1

n
ö
÷
÷
ø
2



 

Une solution est le paradigme Maître/Esclave: Un maître va lancer N esclaves chargés de calculer les sommes partielles,
Pk =
(k+1)*n/N
S
i=k*n/N
1

n
4

1+ æ
ç
ç
è
(i-
1

2
)
1

n
ö
÷
÷
ø
2



 
pour k=0,···,N-1.

C'est une parallélisation par décomposition fonctionnelle (que l'on reverra avec la FFT, section 5.2.2). On pourrait aussi faire de la décomposition de domaine pour calculer une fonction s'appliquant indépendemment à des sous-blocs d'un tableau.

Donc le maître pi_control va lancer N copies d'un esclave pi avec l'entier k correspondant, puis attendre que tous aient terminé leur exécution, enfin effectuer la somme de tous les résultats partiels (voir figure 2.2).



Figure 2.2: Flot de contrôle du calcul parallèle de p

2.2.1  La base pour démarrer: le contrôle des processus.

pvm_mytid

C'est une instruction obligatoire en début de code PVM1 qui permet de raccrocher le processus à un démon pvmd. Sa syntaxe est:

int tid;
tid=pvm_tid();
Cela renvoie tid qui est l'identificateur de tâche du processus. Ce tid permet de définir de façon unique ce processus et de s'adresser à lui. Il y a un cas d'Erreur si cela renvoie tid < 0 (démon PVM inexistant par exemple).

pvm_exit

C'est une instruction (presque) obligatoire en fin de code PVM. Elle permet de dire au démon pvmd que la tâche quitte PVM.

int info;
info=pvm_exit();
Il y a un cas d'Erreur si cela renvoie info < 0.

pvm_spawn

C'est l'instruction qui permet de lancer n tâches PVM exécutant un même code ("programme"):

int numt; 
numt=pvm_spawn(char *task,char **argv,int flag,char 
                          *where,int ntask,int *tids);
On se contentera pour le plus souvent de (voir cependant la section 5.2.2 pour une application de la forme de ``spawn'' la plus générale),

int numt,etid[n];
numt=pvm_spawn("programme",NULL,PvmTaskDefault,NULL,n,&etid[0]);
Le tid de chacune des tâches est rangé dans une entrée du tableau etid. Cette instruction Renvoie numt <= n qui est le nombre de tâche(s) effectivement lancée(s).

Pour les autres champs:

pvm_parent

Cette instruction retourne tid valant le tid de son processus parent (celui qui a crée le processus courant):

int tid; 
tid=pvm_parent();
Elle peut également renvoyer tid=PvmNoParent si le processus courant n'a pas été crée par pvm_spawn.

2.2.2  La base: passage de messages

L'envoi des messages est non-bloquant. Ceci est géré grâce à un tampon d'envoi. Celui-ci doit être,

La réception peut être bloquante ou non-bloquante. On a aussi un tampon pour la réception mais que l'on n'a pas besoin de créer explicitement. On peut,

pvm_initsend

C'est une instruction obligatoire avant l'envoie d'un message: elle initialise le tampon d'envoie des données:

int info; 
info=pvm_initsend(int encoding);
encoding est un entier permettant de spécifier comment coder les données (surtout utile en milieur hétérogène). Ici on emploiera toujours PvmDataDefault. Il y a une Erreur si pvm_initsend renvoie info < 0.

pvm_pk...

Ce sont les fonctions qui effectuent le compactage de différentes données d'un processus, destinées à être envoyées, dans le tampon d'envoi:

int info; 
info=pvm_pkint(int *ip,int nitem,int stride);
Il y a eu une Erreur si cela renvoie info < 0. Il y a une fonction de compactage par type de données possible, donc on trouvera également pvm_pkfloat, pvm_pkdouble, pvm_pkstr etc.

pvm_send

Cette instruction envoie un message de façon non-bloquante:

int info; 
info=pvm_send(int tid,int msgtag);
tid est l'identificateur de processus du destinataire du message. msgtag est un entier identifiant une classe de messages. Cela permet de filtrer les messages (à la réception). Il y a un cas d'Erreur si elle renvoie info < 0.

pvm_upk...

Elles correspondent aux instructions pvm_pk...:

int info; 
info=pvm_upkint(int *ip,int nitem,int nstride);
Elles ont les mêmes arguments que pour les fonctions pvm_pk.... Il y a une Erreur si elles renvoient info < 0.

pvm_recv

Cette instruction reçoit un message de type msgtag de l'expéditeur tid:

int bufid; 
bufid=pvm_recv(int tid,int msgtag);
Elle bloque le processus receveur tant que le message n'est pas reçu. Si tid vaut -1, peut recevoir de n'importe qui. Si msgtag vaut -1, peut recevoir de n'importe qui. Il y a un cas d'Erreur si pvm_recv renvoie info < 0.

2.2.3  L'exemple complet

Le code du maître est:

/* Maitre pi_control.c */
#include <stdio.h>
#include "pvm3.h"
#define NPROC 10
#define N 1000
#define SUMTAG 1
#define INPUTTAG 2

main()
{
int i, n, nproc, numt, bufid, tid[10];
float psum,sum;
n = N;
nproc = NPROC;
printf("I'm t%x spawning %d partial computations\n",pvm_mytid(),NPROC);
numt = pvm_spawn("pi",(char **) 0,PvmTaskDefault,"",NPROC,&tid[0]);
if (numt != NPROC)
{
  printf("could not spawn %d tasks\n",NPROC);
  exit(1);
}
 for (i=0; i<NPROC;i++)
   {
     pvm_initsend(PvmDataDefault);
     pvm_pkint(&i,1,1);
     pvm_pkint(&n,1,1);
     pvm_pkint(&nproc,1,1);
     pvm_send(tid[i],INPUTTAG);
   }
 sum = 0;
 for (i=0; i<NPROC; i++)
   {
     bufid = pvm_recv(tid[i], SUMTAG);
     pvm_upkfloat(&psum,1,1);
     printf("Partial sum received from t%x: %f\n", tid[i], psum);
     sum = sum+psum;
   }
 printf("From master process: Pi = %f\n",sum);
 pvm_exit();
 exit(0);
}
Le code unique des esclaves est:

/* Esclave pi.c */
#include "pvm3.h"
#include <stdio.h>
#define SUMTAG 1
#define INPUTTAG 2

main()
{
  int i, j, n, nproc, bufid, mtid;
  float psum;
  mtid = pvm_parent();
  bufid = pvm_recv(mtid, INPUTTAG);
  pvm_upkint(&i,1,1);
  pvm_upkint(&n,1,1);
  pvm_upkint(&nproc,1,1);
  printf("I am slave %d with parent t%x\n",i,mtid);
  psum = 0;
  for (j=(n/nproc)*i+1;j<=(n/nproc)*(i+1);j++) 
    psum = psum+4.0/(1+(j-.5)*(j-.5)/n/n);
  psum = psum/n;
  pvm_initsend(PvmDataDefault);
  pvm_pkfloat(&psum,1,1);
  pvm_send(mtid, SUMTAG);
  pvm_exit();
  exit(0);
}
Un exemple d'exécution avec NPROC=10 (nombre de processeurs, défini dans le maître) et N=1000 itérations (défini également dans le maître):

pvm> spawn -1 -> pi_control       
[1]
1 successful
t4008b
pvm> [1:t4008b] I'm t4008b spawning 10 partial computations
[1:t4008c] I am slave 0 with parent t4008b
[1:t40091] I am slave 5 with parent t4008b
[1:t40092] I am slave 6 with parent t4008b
[1:t40090] I am slave 4 with parent t4008b
[1:t40093] I am slave 7 with parent t4008b
[1:t4008b] Partial sum received from t4008c: 0.398675
[1:t4008e] I am slave 2 with parent t4008b
[1:t4008c] EOF
[1:t40092] EOF
[1:t40091] EOF
[1:t4008f] I am slave 3 with parent t4008b
[1:t40095] I am slave 9 with parent t4008b
[1:t40093] EOF
[1:t40090] EOF
[1:t4008d] I am slave 1 with parent t4008b
[1:t4008e] EOF
[1:t40095] EOF
[1:t4008f] EOF
[1:t4008b] Partial sum received from t4008d: 0.390908
[1:t4008b] Partial sum received from t4008e: 0.376245
[1:t4008b] Partial sum received from t4008f: 0.356198
[1:t4008b] Partial sum received from t40090: 0.332565
[1:t4008b] Partial sum received from t40091: 0.307088
[1:t4008b] Partial sum received from t40092: 0.281226
[1:t4008b] Partial sum received from t40093: 0.256060
[1:t4008d] EOF
[1:t40094] I am slave 8 with parent t4008b
[1:t4008b] Partial sum received from t40094: 0.232297
[1:t4008b] Partial sum received from t40095: 0.210332
[1:t4008b] From master process: Pi = 3.141593
[1:t40094] EOF
[1:t4008b] EOF
[1] finished

1
On pourra se reporter au livre [GBD+94] pour plus de détails.

Previous Next Contents