TP MBI

Nous avons expérimenté l'utilisation des protocoles MPI pour un problème de multiplication de matrices. énoncé

La stratégie de parallélisation était de distribuer des blocs contigu de lignes et de colonnes, puis faire circuler les colonnes de façon ordonnée pour que chaque processus garde son bloc de lignes et traite (reçoive et transmette) chaque bloc de colonnes une seule fois (moins 1 pour les transmissions). Les blocs de colonnes étaient transposés pour optimiser l'exploitation d'ajacence en cache des données pour opérations successives.

Les trois façons d'implanter l'algorithme étaient en utilisant

  • MPI_Sendrecv_replace.
  • MPI_Bsend et MPI_Recv.
  • MPI_Ibsend et MPI_Irecv.

L'utilisation de Sendrecv_replace est le plus simple: on n'a qu'un simple appel à passer, MPI gère tout du buffer; c'est appel est simple mais bloquant, le programme appelant attend le retour.

Dans l'utilisation de Bsend, c'est notre programme qui gère le passage d'un buffer à Bsend et récupération du buffer de Recv. Bsend est bloquant, le programme appelle ensuite Recv et attend.

Dernière protocole testée, Ibsend/Irecv n'est pas bloquant, mais demande plus d'attention à l'organisation des opérations pour exploiter le “temps masqué” de la circulation pour continuer à calculer, sans risque d'avoir les données locales écrasée par une écriture trop tôt des données attendues.

Les blocs de code pour les parties ainsi variées figurent en annexe.

Performances

Nous avons mesuré les performances sur les PC de salle J3 alors qu'elles n'étaient pas occupées par d'autres processus (sauf si deux tests concouraient sur des mêmes machines accidentellement, mais nous avons fait attention pour éviter cela). Chaque stratégie était testé sur une machine, puis 2, 4, 8, et 16. En dernière instance, ayant constaté que dans ces conditions 4 processus avaient les même performances sur 2 ou 4 machines, 8 processus sur 4 ou 8 machines avaient les mêmes performances, nous avons testé 32 processus sur 16 machines.

Nos mesures sont “loop time” et “Mflops”. La comparaison de “loop time” sur un processus sur la machine maitre permet de calculer le “speed-up” : par quel facteur le temps de calcul est réduit par une configuration donnée. Tenant compte du nombre de processus utilisés pour réaliser un SU permet de calculer l'efficacité. Pour nos tests, nos SU ont avoisinaient 8, mais pour 32 processus cela signifie une efficacité de seulement 25 pour cent!

On note que, lorsqu'une seule machine (à processeur dual core) est utilisée, les performances sont quasi identique pour un processus que pour deux, voir plus rapide pour un seul: visiblement le système d'exploitation utilise les deux cores et on peut en déduire que nos étalonnages sont faux par un facteur de deux, on n'a pas la possibilité de mesurer (facilement) le temps d'un traitement purement séquentiel.

Mflops
Processus 1 2 2 4 8 16 32
Machines self self 2 4 8 16 16
Sendrecv_replace 1045 1024 1295 1827 3299 6090 8115
Bsend Recv 1045 2085 2475 4214 6201 8211
Ibsend Irecv 1036 1032 2063 2472 4879 6488 7611
—-
Efficacité (non ajustée)
Processus 1 2 2 4 8 16 32
Machines self self 2 4 8 16 16
Sendrecv_replace n/a ,49 ,62 ,44 ,40 ,37 ,24
Bsend Recv n/a 1,00 ,59 ,51 ,37 ,21
Ibsend Irecv n/a ,50 1,00 ,60 ,59 ,39 ,23
—-
“Loop time” (secondes)
Processus 1 2 2 4 8 16 32
Machines self self 2 4 8 16 16
Sendrecv_replace 131.5 134.2 106 75,2 41,7 22,6 16,9
Bsend Recv 131,4 65,9 55,5 32,6 22,2 16,7
Ibsend Irecv 132,7 133,2 66,6 55,6 28,2 21,2 18,06

Discussion

Des questions subsistant quant à la performance “de référence” dans chaque batterie de tests, comparons sur la base de deux processus sur chaque machine (base Mflops, tableau suivant).

Processus Machines Sendrecv_replace Bsend Recv Ibsend I recv
Mflops SU Eff. Mflops SU Eff. Mflops SU Eff.
2 self 1024 n/a réf. 1045 n/a réf. 1032 n/a réf.
4 2 1854 1,78 ,89 2241 2.14 1,07 1721 1,67 ,83
8 4 3332 3,22 ,80 2934 2,81 ,70 3302 3,20 ,80
16 8 6090 5,95 ,74 6153 5,89 ,74 6489 6,29 ,78
32 16 8115 7,92 ,49 8211 7,86 ,49 7611 7,35 ,46

Mise à part une apparente anomalie pour 4 processus avec Bsend, les résultats paraissent plus cohérents.

Conclusions

Pour ce problème, pour une configuration de machines homogènes sans autre charges, les performance différent peu entre stratégies. Peut être est-ce la raison pour laquelle Ibsend ne montre pas d'avantage à calculer en temps masqué.

Annexe : Extrait de code

Sendrec_replace

Le code pour MPI_Sendrecv_replace n'était pas optimisé pour le étalonnage avec un seul processus

// Call "OneStepCirculation" and "SeqLocalProduct"

  for (step=0;step<NbPE;step++){
  SeqLocalProduct(step);
  OneStepCirculation(step);
}

void OneStepCirculation(int step)
{
 MPI_Status status;
 MPI_Sendrecv_replace(&A[0][0]
, Blocksize
, MPI_DOUBLE
, (Me-1+NbPE)%NbPE
, 0
      , (Me+1)%NbPE
, 0
, MPI_COMM_WORLD
, &status);

Bsend / Recv

On ne prépare un buffer et n'appelle la routine de circulation des données que lorsque plus d'un processus est utilisé :

if(NbPE>1){
 	int SizeOneMsg;
 	MPI_Pack_size(block_size,MPI_DOUBLE,MPI_COMM_WORLD,&SizeOneMsg);
	int Size = (SizeOneMsg + MPI_BSEND_OVERHEAD);
	double * buf = (double *) malloc(Size); /* Buffer allocation */

 for(step = 0; step < NbPE; step ++){
   SeqLocalProduct(step);
   OneStepCirculation(step, Size, buf);
  }
 free(buf);
}else{ 

for(step = 0; step < NbPE; step ++){
   SeqLocalProduct(step);
  }
}   

La routine de circulation des données :

void OneStepCirculation(int step, int Size, double * buf)
{
  MPI_Status status;
  MPI_Buffer_attach(buf,Size); /* Buffer attachement */
  MPI_Bsend( A, block_size,  MPI_DOUBLE, (Me-1+NbPE)%NbPE, 0, MPI_COMM_WORLD);
  MPI_Recv( A, block_size,  MPI_DOUBLE, (Me+1)%NbPE, 0, MPI_COMM_WORLD, &status);
  MPI_Buffer_detach(&buf,&Size);
}

Ibsend Irecv

void OneStepCirculation(int step, int Size, double * buf)
{
  int i,j;
  MPI_Status statusSend;
  MPI_Status statusRecv;
  MPI_Request requestSend;
  MPI_Request requestRecv;
 
  MPI_Buffer_attach(buf,Size); /* Buffer attachement */
  MPI_Ibsend( A, block_size,  MPI_DOUBLE, (Me-1+NbPE)%NbPE, 0, MPI_COMM_WORLD, &requestSend);
  MPI_Irecv( A2, block_size,  MPI_DOUBLE, (Me+1)%NbPE, 0, MPI_COMM_WORLD, &requestRecv);
  SeqLocalProduct(step);
  MPI_Wait( &requestSend, &statusSend);
  MPI_Wait( &requestRecv, &statusRecv);
  MPI_Buffer_detach(&buf,&Size);
  
  memcpy(A,A2,LOCAL_SIZE*SIZE*sizeof(double));
}

La routine de gestion est peu différente de celle de Bsend :

void ComputationAndCirculation()
{
  int step;
  
  if(NbPE>1){
	int SizeOneMsg;

MPI_Pack_size(block_size,MPI_DOUBLE,MPI_COMM_WORLD,&SizeOneMsg);

int Size = (SizeOneMsg + MPI_BSEND_OVERHEAD);
double * buf = (double *) malloc(Size); /* Buffer allocation */
      for(step = 0; step < NbPE; step ++){
         OneStepCirculation(step, Size, buf);
      }
      free(buf);
  }else{ 
      for(step = 0; step < NbPE; step ++){
         SeqLocalProduct(step);
      }
}

 
m2ilc/parallelisme_mpi.txt · Dernière modification: 2011/04/10 15:04 par suitable