WOW !! MUCH LOVE ! SO WORLD PEACE !
Fond bitcoin pour l'amélioration du site: 1memzGeKS7CB3ECNkzSn2qHwxU6NZoJ8o
  Dogecoin (tips/pourboires): DCLoo9Dd4qECqpMLurdgGnaoqbftj16Nvp


Home | Publier un mémoire | Une page au hasard

 > 

Environnements de grappes de calcul intensif sur réseaux d'entreprise: déploiement, exploitation et performances

( Télécharger le fichier original )
par Franklin TCHAKOUNTE
Université de Ngaoundéré - Master en Systèmes et Logiciels en Environnements Distribués 2010
  

précédent sommaire suivant

Bitcoin is a swarm of cyber hornets serving the goddess of wisdom, feeding on the fire of truth, exponentially growing ever smarter, faster, and stronger behind a wall of encrypted energy

Chapitre IV

EVALUATION DU MODELE SUR UNE

APPLICATION

Ce chapitre est la phase la plus pratique de notre travail. Il est constitué du déploiement de la grappe, de l'implémentation de l'ordonnancement d'un exemple sur cette grappe et du calcul des performances d'une réseau (latence, débit) d'une part et d'autre part de l'analyse des performances de l'application sur la grappe. Nous commencerons par présenter notre grappe, ensuite nous calculerons ses caractéristiques comme décrit dans le modèle, et par la fin nous analyserons les performances d'une application parallèle sur la grappe.

1 Présentation de l'architecture de notre grappe

Notre grappe est constituée de cinq stations de travail, reliées par un switch de 12 ports. La figure IV.1 l'illustre clairement. Pour le déploiement de notre grappe, des outils ont été installés à la base. - Nous avons installé un gestionnaire de tâches : OAR.

- le service NIS1 : qui a pour rôle de distribuer les informations sur les utilisateurs et même sur les postes à travers toute la grappe. Ainsi, tout utilisateur ayant un compte sur le serveur NIS pourra se logger sur n'importe quelle machine de la grappe.

- Le service NFS2 : On l'utilise en corrélation avec le serveur NIS pour distribuer le répertoire utilisateur sur les autres machines. Ainsi, tous les répertoires utilisateurs sur le serveur NFS seront accessibles sur n'importe quelle machine de la grappe.

- Le service SSH nécessaire pour se logger à distance.

- La bibliothèque de communications MPICH2 qui nous permettra de paralléliser les applications. L'installation de toutes ces composantes est précisée en annexe. Les caractéristiques des stations de travail sont récapitulées dans le tableau ci-dessous :

1network information service 2network file system

Figure IV.1 - Notre grappe

 

Processeur

Mémoire

Nom/adresse IP

SE

Machine1

Pentium 4, 3GHZ

512Mo

host1.labomaster/192.168.12.1

Debian 4.0

Machine2

Intel Celeron, 2GHZ

256Mo

host2.labomaster/192.168.12.2

Debian 4.0

Machine3

Pentium Dual @2GHZ

2Go

server.labomaster/192.168.12.3

Ubuntu 9.4

Machine4

Pentium 4,3.40GHZ

1Go

host3.labomaster/192.168.12.4

Debian 5.0

Machine5

Pentium 4,3GHZ

1Go

host4.labomaster/192.168.12.5

Debian 5.0

La machine 3 représente à la fois le serveur OAR, le serveur NIS, le serveur NFS et le serveur DNS. MPICH2 a été installé sur ce poste et est dupliqué via NFS sur les autres postes. Chacune de ces machines sont interconnectées par un réseau fast Ethernet 100Mbits/s. D'après notre modèle, une grappe hétérogène est modélisée par un graphe où les sommets sont les stations de travail caractérisées par la puissance du processeur, la mémoire et l'impact de la pile réseau. Dans le suite, nous décrirons comment nous avons estimé la troisième caractéristique.

2 Calcul des caractéristiques des noeuds du graphe de grappe

Lorsqu'une trame sort ou entre dans un hôte, elle traverse toutes les couches de la pile réseau et cela engendre un coût non négligeable.

2.1 Les surcoûts engendrés par la pile réseau relatifs à chaque station de travail

Il a été difficile de trouver un outil pour évaluer l'impact du protocole réseau; nous avons écrit un algorithme basé sur les sockets pouvoir évaluer les coûts engendrés par chaque ordinateur. Cet algorithme est basé sur le modèle client/serveur avec pour protocole UDP3 où le client envoie un message de 8 octets au serveur qui est déjà lançé. Ici le client et le serveur sont sur un même poste. Le code que nous avons écrit est le suivant.

CODE DU CLIENT

3User Datagram Protocol

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/un.h> #include<time.h>

int main (int argc, char* argv[]) {

struct timeval start,m;

struct sockaddr_in id; char s[3];

int id_socket,tempo,envoi,i=1,argument;

int erreur;

id_socket=socket(AF_INET,SOCK_DGRAM,0);

if (id_socket<0)

{perror("\nErreur dans la création du socket");

exit(1);

}

else

printf("\n socket : OK"); id.sin_family=AF_INET; id.sin_addr.s_addr=htonl(INADDR_ANY);

id.sin_port=htons(333); tempo=sizeof(id);

argument=atoi(argv[1]); while(i<=argument)

{

gettimeofday(&start,NULL); envoi=sendto(id_socket,&start,sizeof(start),0,(struct sockaddr*)&id,tempo);

i++;

sleep(1);

}

erreur=close(id_socket); exit(0);

return 0;

}

CODE DU SERVEUR

....

....

#define minim(a,b) ((a)<(b)?a:b)
#define maxim(a,b) ((a)>(b)?a:b)

int main (int argc, char* argv[]) {

id_de_la_socket=socket(AF_INET,SOCK_DGRAM,0);

if (id_de_la_socket<0)

{perror("\nErreur dans la création du socket");

exit(1);

}

else

printf("\n socket : OK"); is.sin_family=AF_INET; is.sin_addr.s_addr=htonl(INADDR_ANY);

is.sin_port=htons(333); tempo=sizeof(is);

erreur=bind(id_de_la_socket,(struct sockaddr*)&is,sizeof(is));

if (erreur<0)

{

perror("\nserver: bind\n"); exit(1);

}

else

printf("\nbind : OK\n"); printf("\n==NbrEnvoi=====MinRTT==================MaxRTT================MoyenneRTT\n"); max=0;

min=1000000;

argument=atoi(argv[1]); while(i<=argument)

{//printf("%d",i);

reception=recvfrom(id_de_la_socket,&start,sizeof(start),0,(struct sockaddr*)&is,&tempo); gettimeofday(&end,NULL);

res=(end.tv_sec-start.tv_sec)*1000000 + (end.tv_usec-start.tv_usec);

min=minim(min,res);

max=maxim(max,res);

moy=moy+res;

printf("Temps mis=%ld microsecondes \n",res);

i++;

}

printf("\n==%d===%d====%d===%1.f\n",(atoi(argv[1])),min,max,(moy/(atoi(argv[1])))); erreur=close(id_de_la_socket);

....

}

Le surcoût de la pile est considéré comme le temps mis par le message en local. Nous avons effectué l'envoi de 1.521.110 messages de 8 octets par machine à des moments différents. Après ces envois, nous avons recueilli les temps mis par le transfert en utilisant la fonction gettimeofdate et nous avons effectué la moyenne pour avoir une estimation fine.

2.2 Tableau récapitulatif et discussion

Nous obtenons donc la tableau ci-dessous qui regroupe tous les impacts correspondants aux différentes machines.

 

Processeur

Mémoire

Coût induit par la pile(microsecondes)

Machine1

Pentium 4, 3GHZ

512Mo

23

Machine2

Intel Celeron, 2GHZ

256Mo

32

Machine3

Pentium Dual @2GHZ

2Go

5

Machine4

Pentium 4,3.40GHZ

1Go

31

Machine5

Pentium 4,3GHZ

1Go

12

On pourrait croire que l'impact du protocole réseau est proportionnel à la vitesse de traitement des processeurs mais la tableau nous présente le contraire. En effet, nous remarquons que les machines 1 et 5 ayant les mêmes vitesses de traitement induisent des coûts tout à fait différents. Celui de la machine 1 est à peu près le double de celui de la machine 5. D'autre part, la machine 4 ayant une vitesse d'exécution supérieure à celles des machines 1 et 5 induit un coût qui est presque la somme des coûts relatifs aux machines 1 et 2. Ceci nous amène à penser que les raisons plausibles sont liées aux activités du processeur au moment du calcul et à la gestion du protocole réseau par le système d'exploitation puisqu'on note que les systèmes d'exploitation sont différents. Le système d'exploitation aurait passer la main à un autre processus différent du notre pour un temps.

3 Calcul des caractéristiques des arcs du graphe de grappe

Pour évaluer les latences des liens, nous avons également écrit un programme basé sur les sockets tandis que pour évaluer les débits des liens nous avons utilisé des outils existants.

3.1 Evaluation des latences

Comme nous l'avons dit précédemment, nous avons écrit un programme sur le principe client/serveur basé sur les sockets en utilisant le protocole UDP mais cette fois on calcule le temps d'une requête réponse par le client c'est à dire que le client fait émet un message de 8 octets au serveur et attend la réponse de ce dernier. Il faut noter ici que le client et le serveur sont sur des postes différents. La latence est donc le temps requête/réponse UDP ou le RTT(Round trip time). Le code que nous avons fait est fourni ci-dessous :

Code du client

.... .... #define minim(a,b) ((a)<(b)?a:b)

#define maxim(a,b) ((a)>(b)?a:b)

....

int erreur;

int main (int argc, char* argv[])

{

....

4celui lancé pour exécuter le programme

....

int id_socket,tempo,envoi,i=1,reception;

id_socket=socket(AF_INET,SOCK_DGRAM,0);

if (id_socket<0)

{perror("\nErreur dans la création du socket\n");

exit(1);

}

else printf("\n socket : OK\n");

id.sin_family=AF_INET; id.sin_addr.s_addr=inet_addr("adresse_du_serveur");

id.sin_port=htons(3333); tempo=sizeof(id);

printf("\n==NbrEnvoi=====MinRTT======MaxRTT=====MoyenneRTT\n");

max=0;

min=100000;

argument=argv[1];

while(i<=argument)

{gettimeofday(&start,NULL); envoi=sendto(id_socket,&start,sizeof(start),0,(struct sockaddr*)&id,tempo); reception=recvfrom(id_socket,&end,sizeof(end),0,(struct sockaddr*)&id,&tempo); gettimeofday(&endl,NULL);

res=(endl.tv_sec-start.tv_sec)*1000000 + (endl.tv_usec-start.tv_usec); min=minim(min,res);

max=maxim(max,res);

moy=moy+res;

i++;

//sleep(3);

} printf("\n=====%d======%d====%d====%1.f\n",atoi(argv[1]),min,max,(moy/(atoi(argv[1])))); erreur=close(id_socket);

exit(0);

return 0;

Code du serveur

int main (int argc, char* argv[])

{

struct timeval end,start;

long int res;

int erreur,tempo,id_de_la_socket,reception,n,i=1,envoi; struct sockaddr_in is;

id_de_la_socket=socket(AF_INET,SOCK_DGRAM,0); if (id_de_la_socket<0)

{perror("\nErreur dans la création du socket\n"); exit(1);

}

else

printf("\n socket : OK\n");

is.sin_family=AF_INET;

is.sin_addr.s_addr=htonl(INADDR_ANY); is.sin_port=htons(3333);

tempo=sizeof(is);

erreur=bind(id_de_la_socket,(struct sockaddr*)&is,sizeof(is)); if (erreur<0)

{

perror("\nserver: bind\n");

exit(1);

}

else

printf("\nbind : OK\n");

//gettimeofday(&end,NULL);

argument = atoi(argv[1]);

while(i<=argument)

{

reception=recvfrom(id_de_la_socket,&start,sizeof(start),0,(struct sockaddr*)&is,&tempo); gettimeofday(&end,NULL);

envoi=sendto(id_de_la_socket,&end,sizeof(end),0,(struct sockaddr*)&is,tempo);

i++;

}

erreur=close(id_de_la_socket);

Nous avons effectué l'envoi de 1.521.110 messages de 8 octets par lien à des moments différents. Après chaque envoi, nous avons recueilli les temps mis par le transfert requête/réponse en utilisant la fonction gettimeofdate. Après tous les envois, nous avons effectué la moyenne de tous les résultats trouvés pour avoir une estimation fine. Nous avons obtenu les latences regroupées dans la tableau ci-dessous correspondantes aux liens entre les hôtes. Elles sont exprimées en microsecondes.

 

Machine1

Machine2

Machine3

Machine4

Machine5

Machine1

x

272

107

127

125

Machine2

223

x

266

187

184

Machine3

123

255

x

260

91

Machine4

29

398

127

x

148

Machine5

126

355

100

132

x

La latence d'une machine M1 à une machine M2 n'est pas forcément la même de M2 à M1. Ceci est dû aux performances des processeurs qui sont différentes et au fait que les systèmes d'exploitation gèrent la pile réseau différemment. Il faut noter que le paquet arrive au niveau du commutateur qui étant donné son fonctionnement mettra un temps pour faire transiter le message.

3.2 Evaluation des débits des liens

Pour évaluer les débits des liens nous avons utilisé les outils netperf et iperf. Nous avons utilisé iperf pour confirmer les débits trouvés avec netperf. La syntaxe de la commande avec netperf est :

#netperf -H <adresse de la machine destinatrice> Le resultat est de la forme

Recv Send Send

Socket Socket Message Elapsed

Size Size Size Time Throughput

bytes bytes bytes secs. 10^6bits/sec

On a obtenu les résultats suivants en Mbits/s :

 

Machine1

Machine2

Machine3

Machine4

Machine5

Machine1

x

94

87

94

83

Machine2

94

x

73

94

77

Machine3

65

81

x

90

94

Machine4

87

90

82

x

88

Machine5

86

82

94

82

x

3.2.1 Discussion

Le débit d'une machine M1 à une machine M2 n'est pas forcément le même de M2 à M1. Ceci peut être dû aux différences entre les vitesses de traitement des processeurs et aux charges des processeurs à un moment donné. Si un processeur est trop surchargé par rapport à l'autre, alors il y aura influence sur le débit. Par ailleurs, on voit que les 100 Mbits/s constituant le débit du réseau Fast-Ethernet n'est jamais atteint. On a en moyenne 85.85 Mbits/s. Cela peut être dû au fait qu'il y a des collisions sur le réseau entrainant ainsi des pertes de messages.

4 Programmation de l'application

Nous avons choisi de paralléliser la multiplication d'une matrice A par un vecteur V . 4.1 Algorithme

Le principe est le suivant : Le processus 0 est le processeur maître et détient la matrice et le vecteur. Les autres processus n'ont que le vecteur. Initialement le maître découpe la matrice et envoie une ligne de la matrice aux autres processeurs. Dès que le processeur termine d'exécuter la tâche qui est le produit d'une ligne reçue par le vecteur, il renvoie le résultat au maître. Le maître chaque va tester s'il y a encore une ligne non envoyée et si tel est le cas, il envoie à un processeur libre. Le code est le suivant :

4.2 Code

#define min(a,b) ((a)<(b)?a:b)

void initialiser(double m[SIZE][SIZE])

{

int i,j;

for(i=0;i<SIZE;i++) for(j=0;j<SIZE;j++) m[i][j]=1.0;

}

void maitre(int numprocs)

{

int i,j, sender,row,numsent=0; double a[SIZE][SIZE],c[SIZE]; double r;

MPI_Status status;

initialiser(a);

for(i=1;i<min(numprocs,SIZE);i++)

{

MPI_Send(a[i-1],SIZE,MPI_DOUBLE,i,i,MPI_COMM_WORLD);

numsent++;

}

for(i=0;i<SIZE;i++)

{

MPI_Recv(&r,1,MPI_DOUBLE,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status); sender=status.MPI_SOURCE;

row= status.MPI_TAG-1;

c[row]=r;

if(numsent<SIZE){ MPI_Send(a[numsent],SIZE,MPI_DOUBLE,sender,numsent+1,MPI_COMM_WORLD);

numsent++;

} else

MPI_Send(MPI_BOTTOM,0,MPI_DOUBLE,sender,0,MPI_COMM_WORLD);

}

}

void esclave()

{

double b[SIZE], c[SIZE];

int i, row, myrank;

double r;

MPI_Status status;

for(i=0;i<SIZE;i++) b[i]=2.0;

MPI_Comm_rank(MPI_COMM_WORLD,&myrank);

if(myrank<=SIZE)

{

MPI_Recv(c,SIZE,MPI_DOUBLE,0,MPI_ANY_TAG,MPI_COMM_WORLD,&status); while(status.MPI_TAG>0)

{

row=status.MPI_TAG-1;

r=0.0;

for(i=0;i<SIZE;i++)

r+=c[i]*b[i];

MPI_Send(&r,1,MPI_DOUBLE,0,row+1,MPI_COMM_WORLD); MPI_Recv(c,SIZE,MPI_DOUBLE,0,MPI_ANY_TAG,MPI_COMM_WORLD,&status);

}

} } int main (int argc, char **argv){

int myrank,p;

MPI_Init (&argc, &argv);

MPI_Comm_rank (MPI_COMM_WORLD, &myrank);

MPI_Comm_size (MPI_COMM_WORLD, &p);

double startwtime = 0.0, endwtime;

char processor_name[MPI_MAX_PROCESSOR_NAME];

int namelen;

MPI_Get_processor_name(processor_name,&namelen);

fprintf(stdout,"Le Processus %d parmi %d est sur %s\n",myrank,p, processor_name); MPI_Barrier(MPI_COMM_WORLD);

if(myrank==0) {startwtime = MPI_Wtime();parent(p);}

else enfant();

MPI_Barrier(MPI_COMM_WORLD);

if(myrank==0)

{endwtime = MPI_Wtime();

printf("TEMPS DE CALCUL = %f\n", endwtime-startwtime);

}

...

Le code séquentiel du même problème est fournit en annexe. Pour calculer son temps d'exécution, il suffit de faire

#time ./executable

Nous avons utilisé MPICH2 pour réaliser cette application. Cependant l'ordonnancement que nous fait est fixée au départ et ne change pas tout au long de l'exécution.

5 Ordonnancement sur la grappe

L'ordonnancement est statique c'est à dire qu'il ne change pas tout au long de l'exécution de l'algorithme. Toutes les machines sont utilisées pour l'ordonnancement. La machine server est la machine sur laquelle s'exécute le premier processus. L'ordre d'ordonnancement est server - host2 - host3 - host4 - host1 : le premier processus s'exécute sur la machine server, le second sur la machine host2, le troisième sur la machine host3, le quatrième sur la host4 et le dernier sur la machine host1. S'il y

a encore des processus, alors on reprend avec la machine server et ainsi de suite. Exemple d'exécution avec 8 processus :

user@labofs:~/Desktop/codes$ mpdtrace // Pour avoir l'ordre d'ordonnancement

server

host2

host3

host4

host1

user@labofs:~/Desktop/codes$mpiexec -n 8 av Le Processus 0 parmi 8 est sur server

Le Processus 2 parmi 8 est sur host3 Le Processus 5 parmi 8 est sur server Le Processus 3 parmi 8 est sur host4 Le Processus 4 parmi 8 est sur host1 Le Processus 7 parmi 8 est sur host3 Le Processus 1 parmi 8 est sur host2 Le Processus 6 parmi 8 est sur host2 TEMPS DE CALCUL = 0.054822

5.1 Résultats et discussion

Nous avons obtenu les résultats suivants avec 5, 6, 8, 10, 12, 15, 20, 25, 30, 32, 40, 50, 100 processus.

Nous avons travaillé sur une matrice de tailles n=100, 1000, 3000, 5000 et 8000. Nous dénotons par T _par le temps parallèle, T_seq le temps séquentiel, Acc l'accélération et Eff l'efficacité.

Le temps de calcul séquentiel pour n=100, 1000, 3000, 5000 et 8000 sont respectivement 0.016 secondes, 0.042 secondes, 0.083 secondes, 0.092 secondes et 0.11 secondes sur la machine 3.

pour n=100 -*

Processus

 

T_par(s)

Acc(T_seq/T_par)

Eff

5

0.69

0.02

0.004

6

0.06

0.26

0.04

8

0.05

0.32

0.04

10

0.07

0.22

0.02

12

0.08

0.20

0.01

15

0.09

0.17

0.01

20

0.14

0.11

0.005

25

0.19

0.08

0.003

30

0.3

0.05

0.001

32

0.36

0.04

0.001

40

0.55

0.029

0.0007

50

1.06

0.01

0.0003

100

6.1

0.02

0.00002

pour n=1000 -*

Processus

 

T_par(s)

Acc(T_seq/T_par)

Eff

5

0.006

7

1.14

6

0.007

6

1

8

0.008

5.25

0.65

10

0.009

4.66

0.46

12

0.0095

4.42

0.36

15

0.0072

5.83

0.38

20

0.0075

5.6

0.28

25

0.3

0.14

0.0056

30

0.42

0.1

0.0033

32

0.5

0.084

0.0026

40

1.1

0.038

0.0009

50

2.4

0.017

0.00035

100

6.29

0.0066

0.00006

pour n=3000 -*

Processus

 

T_par(s)

Acc(T_seq/T_par)

Eff

5

0.004

20.75

4.15

6

0.003

27.66

4.61

8

0.0032

25.93

3.24

10

0.002

41.5

4.15

12

0.0021

39.52

3.29

15

0.0023

36.08

2.40

20

0.0025

33.2

1.66

25

0.003

27.06

1.10

30

0.3

0.27

0.009

32

0.47

0.17

0.005

40

0.7

0.11

0.002

50

2.04

0.040

0.0008

100

3.5

0.023

0.0002

pour n=5000 -*

Processus

 

T_par(s)

Acc(T_seq/T_par)

Eff

5

0.0033

27.8

5.57

6

0.0034

27.05

4.50

8

0.0036

25.5

3.19

10

0.0038

24.21

2.42

12

0.0027

34.07

2.83

15

0.002

46

3.06

20

0.0024

38.33

1.91

25

0.0038

24.21

0.96

30

0.47

0.195

0.006

32

0.473

0.194

0.006

40

0.5

0.184

0.0046

50

3.04

0.030

0.0006

100

5.5

0.016

0.0001

n=8000 -?

Processus

 

T_par(s)

Acc(T_seq/T_par)

Eff

5

0.0036

30.55

6.11

6

0.0037

29.41

4.90

8

0.0038

28.94

3.61

10

0.004

27.5

2.75

12

0.0017

64.70

5.39

15

0.0022

50

3.33

20

0.0024

45.83

2.29

25

0.0038

28.94

1.15

30

0.47

0.23

0.007

32

0.77

0.14

0.004

40

0.6

0.18

0.004

50

4.02

0.027

0.0005

100

7.9

0.013

0.0001

Figure IV.2 - Courbe représentant l'accélération de l'algorithme parallèle

Nous remarquons qu'avec une matrice de taille 100, l'accélération est presque nulle; cela veut dire que l'algorithme parallèle est inutile puisque le temps d'exécution en parallèle est plus grand que le temps séquentiel. Avec une matrice de taille 1000, on constate une amélioration des performances avec des processus allant de 5 à 20 processus. Et on trouve que l'algorithme est efficace avec 5 et 6 processus. Lorsqu'on passe aux tailles 2000, 3000, 8000, on remarque que l'algorithme est plus efficace lorsqu'on parallélise avec 20 ou 25 processus. Aussi, l'algorithme accélère avec 5, 6, 8, 10, 12, 15, 20, 25 processus bien qu'elle ne fournit pas une bonne efficacité dans ces cas. Lorsqu'on excède 25 processus, l'accélération et l'efficacité se dégradent rapidement jusqu'à zéro. En effet, le temps de calcul parallèle est alors considérablement réduit lorsque le nombre de processus augmente ceci les processus passent plus de temps à communiquer qu'à calculer.

Figure IV.3 - Courbe représentant l'efficacité de l'algorithme parallèle

précédent sommaire suivant






Bitcoin is a swarm of cyber hornets serving the goddess of wisdom, feeding on the fire of truth, exponentially growing ever smarter, faster, and stronger behind a wall of encrypted energy








"Piètre disciple, qui ne surpasse pas son maitre !"   Léonard de Vinci