Mémoire final du projet TER Programmation performante d ...

61
emoire final du projet TER Programmation performante d’architectures parall` eles h´ et´ erog` enes Romain JAMET Cl´ ement LEGER edric LUKIC Vincent PARROD Encadrants : Sylvain JUBERTIE, Fr´ ed´ eric LOULERGUE et J.M. COUVREUR M1 Informatique - Universit´ e d’Orl´ eans - Ann´ ee 2009/2010

Transcript of Mémoire final du projet TER Programmation performante d ...

Page 1: Mémoire final du projet TER Programmation performante d ...

Memoire final du projet TER

Programmation performanted’architectures paralleles heterogenes

Romain JAMET

Clement LEGERCedric LUKIC

Vincent PARROD

Encadrants : Sylvain JUBERTIE, Frederic LOULERGUE et J.M. COUVREUR

M1 Informatique - Universite d’Orleans - Annee 2009/2010

Page 2: Mémoire final du projet TER Programmation performante d ...
Page 3: Mémoire final du projet TER Programmation performante d ...

Table des matieres

1 Resume du projet 5

2 Introduction au domaine 72.1 Architectures paralleles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.2 Meta-programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

3 Analyse de l’existant 193.1 HMPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3.2 NT2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4 Besoins non fonctionnels 234.1 Liste des besoins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2 Problemes techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5 Besoins fonctionnels 255.1 Prototype papier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

5.2 Liste des besoins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

5.3 Problemes techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

6 Premiere experimentation 296.1 Probleme des N-Corps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

6.2 Implementations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

6.3 Evaluation des performances . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7 Exemples de fonctionnement 397.1 Algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

7.2 Lancement du programme principal . . . . . . . . . . . . . . . . . . . . . . . 39

8 L’architecture 418.1 Architecture du logiciel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

8.2 Structure du programme a evaluer . . . . . . . . . . . . . . . . . . . . . . . . 42

9 Commentaires techniques 459.1 Structure d’un programme en entree . . . . . . . . . . . . . . . . . . . . . . . 45

9.2 Le script Optimize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

10 Analyse en complexite 49

11 Resultat des tests de validation et de fonctionnement 51

Page 4: Mémoire final du projet TER Programmation performante d ...

12 Description des extensions possibles 53

13 Planning 55

14 Annexes 59

Page 5: Mémoire final du projet TER Programmation performante d ...

Chapitre 1

Resume du projet

Programmation performante d’architectures paralleles heterogenes

Les architectures actuelles contiennent plusieurs niveaux de parallelisme : unites vectoriellesde type SSE, multi-cœurs, multi-processeurs, accelerateurs de type GPU, processeur Cell, etc.Cette heterogeneite materielle est accompagnee par une heterogeneite logicielle et conceptuelle.En effet, chaque niveau necessite une bibliotheque differente et eventuellement une approchede parallelisation differente : parallelisme de taches et/ou de donnees, memoire partagee ouechange de messages. Un code optimise est donc specifique a une configuration materielle etdoit etre reecrit afin de l’adapter/optimiser pour une architecture differente.

Le probleme qui nous interesse est de pouvoir programmer des codes s’adaptant a unearchitecture donnee de maniere efficace. L’idee est d’isoler les parties du code specifique pou-vant beneficier d’une acceleration et d’en proposer differentes implantations pour differentsaccelerateurs. Les implantations permettant les meilleures performances de l’application sontensuite choisies et inserees a la compilation dans le programme en fonction de l’architecturecible.

L’objectif est de developper une approche de programmation haut-niveau permettant d’adap-ter facilement un code a une architecture parallele en isolant le code specifique et en proposantplusieurs implantations (sequentielle, SSE, OpenMP, MPI, CUDA). Cette architecture devraetre validee sur des exemples de codes de type simulation d’ecoulement de fluide, n-body,ou moteur physique. L’etape suivante est d’associer a chaque implantation un cout et dedeterminer, en fonction de l’architecture cible, les implantations minimisant le cout globalde l’application. L’application est donc generique et sa compilation produit une applicationoptimisee pour l’architecture cible.

5

Page 6: Mémoire final du projet TER Programmation performante d ...
Page 7: Mémoire final du projet TER Programmation performante d ...

Chapitre 2

Introduction au domaine

2.1 Architectures paralleles

Il existe differentes familles d’architectures paralleles

• SIMD (Single Instruction on Multiple Data) : plusieurs donnees sont traitees en memetemps par une seule instruction.• MISD (Multiple Instruction on Single Data) : une donnee unique est traitee par plusieurs

instructions. Il s’agit d’architectures dites ”pipeline”.• MIMD (Multiple Instruction on Multiple Data) : on execute une instruction differente

sur chaque processeur pour des donnees differentes.• SPMD (Single Program Multiple Data) : ce n’est pas une architecture, mais un mode

d’execution (on execute la meme application sur tous les processeurs)

2.1.1 Une implementation SIMD : SSE

SSE, l’abreviation de Streaming SIMD Extensions, est un jeu de 70 instructionssupplementaires pour microprocesseurs x86 apparu en 1999 [9]. Le SSE, a l’origine, a ajoutehuit nouveaux registres 128 bits nommes XMM0 a XMM7 (pour les architectures 32 bits), quipermettent de contenir plusieurs donnees chacun (par exemple, 4 entiers simple precision, 8entiers de type short). Les architectures 64 bits possedent 8 registres supplementaires, ce quifait un total de 16 registres. On retrouve aussi bien des instructions arithmetiques, logiques,de comparaison, que de manipulation de donnees.

L’interet est d’effectuer plusieurs operations simultanement, a l’aide d’instructionsspecifiques. Ces instructions sont directement utilisables depuis certains langages haut-niveaucomme C++ via l’utilisation d’ intrinsics [8] (interface entre un langage de haut-niveau etdu code assembleur).Ceci permet, de plus, de garder une certaine portabilite, sous reserve que l’architecture supportela technologie.

Par exemple, si nous souhaitons additionner 2 vecteurs d’entiers de simple precision de taille4 octets, nous aurions normalement besoin de 4 operations. En utilisant les instructions offertespar SSE, nous pouvons le faire en une seule.

7

Page 8: Mémoire final du projet TER Programmation performante d ...

2.1.2 Processeurs multicores

L’approche MPI (Message-Passing Interface)

MPI, concue en 1993-94[11], est une norme definissant une bibliotheque de fonctionsutilisee en C, C++ et Fortran. Elle permet d’exploiter des architectures multi-processeurs parpassage de messages. Ce fut l’un des premiers standards en terme de programmation parallelesur des systemes a memoire distribuee et le premier base sur une communication par passagede messages.

Il existe deux implementations differentes sous licence libre, OpenMPI et MPICH, tous deuxayant evoluees avec le temps sous differentes versions. L’implementation choisie pour le projetest OpenMPI.MPI a ete concu pour etre utilise aussi bien sur des machines massivement paralleles a memoirepartagee que sur des clusters d’ordinateurs a memoire distribuee.

Les programmes respectent l’approche SPMD (Single Program Multiple Data), c’est-a-direqu’ils sont executes par tous les processeurs, ils effectuent tous les memes taches mais surdes donnees differentes. Sachant que chaque processus travaille sur sa propre memoire, ilspeuvent s’envoyer des donnees par passage de messages via un communicateur (Figure 2.1).Cette methode de programmation necessite de la part de l’utilisateur de coder en dur laparallelisation de son application.Un communicateur designe un ensemble de processus (ou processeurs) pouvant communiquerensemble. Deux processus ne peuvent communiquer ensemble que s’ils sont dans un memecommunicateur (Figure 2.1). Par defaut, le communicateur qui englobe tous les processus estMPI COMM WORLD [13].

En theorie, la parallelisation en MPI permet de diviser au maximum le temps de calcul parle nombre de cœurs de processeurs presents sur la machine. Ce but est legerement revus a labaisse a cause des echanges de messages qui s’operent entre les cœurs. De plus, ces echangespeuvent etre dans certains cas plus importants que le temps propre aux calculs.

m1 m2

p1

mn­1 mn

pnpn­1p2

Mémoires

Processus

Programme

Communication

Fig. 2.1 – Communication par passage de messages entre differents processeurs

Page 9: Mémoire final du projet TER Programmation performante d ...

L’exemple ci-dessous presente la fonction MPI ALLGATHER :

Listing 2.1 – Passage de tableaux en MPI

#i n c l u d e <s t d i o . h>#i n c l u d e ”mpi . h”i n t ∗ tab , ∗ bufTab ;i n t nb , p i d ;i n t main ( i n t argc , c h a r ∗ a r g v [ ] ) {

// I n i t i a l i s e r de MPIM P I I n i t (&argc , & a r g v ) ;//On v e r i f i e l e nombre de p r o c e s s u s p r e s e n t sMPI Comm size (MPI COMM WORLD, &nb ) ;// Determine son rang dans l e groupe WORLDMPI Comm rank (MPI COMM WORLD, & p i d ) ;s r a n d ( p i d ∗ t ime (NULL) ) ;tab = new i n t [ 4 ] ;memset ( tab , 0 , 4∗ s i z e o f ( i n t ) ) ;

f o r ( i n t i = 0 ; i < 4 ; i ++){tab [ i ] = p i d ;

}

bufTab = new i n t [ 4∗ nb ] ;memset ( bufTab , 0 , 4∗nb∗ s i z e o f ( i n t ) ) ;M P I A l l g a t h e r ( tab , 4 , MPI INT , bufTab , 4 , MPI INT ,

MPI COMM WORLD) ;

i f ( p i d == 0) {f o r ( i n t j = 0 ; j < 4∗nb ; j ++){

s t d : : cout << ” bufTab [ ” << j << ” ] = ” << bufTab [ j ] <<s t d : : e n d l ;

}}

M P I F i n a l i z e ( ) ;}

Chaque processus declare un tableau ”tab” de taille 4, puis chacun le remplit avec leurvaleur de pid, c’est-a-dire 0 pour le processus 0, 1 pour le 1, etc. Ensuite, ils declarent tousun tableau ”bufTab” de taille egale a 4 fois le nombre de processus utilises, qui servira areceptionner toutes les donnees qu’ils recevront des autres processus. Cette fonction permet atous les processus de collecter tous les tableaux ”tab” provenant de tous les autres, eux-memecompris. Ils les receptionnent donc dans le tableau ”bufTab”.

Le test if (pid == 0) permet de verifier que les envois ont bien ete faits et que letableau ”bufTab” a bien receptionne les donnees. Seul le processus 0 affiche son tableau.

Page 10: Mémoire final du projet TER Programmation performante d ...

Voici un exemple d’execution du programme ci-dessus en utilisant 2 cœurs :

Listing 2.2 – Affichage en console

bufTab [ 0 ] = 0bufTab [ 1 ] = 0bufTab [ 2 ] = 0bufTab [ 3 ] = 0bufTab [ 4 ] = 1bufTab [ 5 ] = 1bufTab [ 6 ] = 1bufTab [ 7 ] = 1

On s’apercoit que les 4 premieres valeurs sont celles du tableau ”tab” du processus 0 etles 4 autres sont celles du processus 1, l’echange de donnees entre ces deux processus a bienete fait.

Page 11: Mémoire final du projet TER Programmation performante d ...

L’approche OpenMP

Un programme OpenMP est une alternance de regions sequentielles et de regions paralleleslimitees par des annotations (#pragma) [14][6]. Dans une region parallele plusieurs processusse partageant le travail peuvent etre executes en meme temps.

Tout au debut est execute un processus unique (processus maıtre dont le rang est 0) quiactive des processus legers (threads) a l’entree d’une region parallele (Schema 2.2). Cesthreads vont alors executer un ensemble d’instructions se trouvant dans cette zone parallele[5].Dans cette derniere, une variable peut-etre lue et modifiee en memoire [7]. Elle peut etreprivee (chaque processus a sa propre variable) ou partagee (tous les processus se partagentune meme variable). C’est le systeme d’exploitation qui va affecter les taches aux processeurs.

Le partage du travail consiste essentiellement a :

• executer une boucle par repartition des iterations entre les taches.• executer plusieurs sections de code mais une seule par tache.• executer plusieurs occurrences d’une meme procedure par differentes taches.

Les avantagesOPENMP a pour avantage de nous permettre de paralleliser un programme tres facilement.

En effet, nous n’avons pas a gerer explicitement les communications entre les processus.De plus, il s’ajoute tres facilement a un code sequentiel existant tout en respectant sasemantique. Nous avons juste a rajouter quelque directives et le programme est parallelise.Dans le cas ou le compilateur ne supporterait pas OPENMP, il ignorera pendant la compilationtoutes les directives OPENMP du programme.

Les inconvenientsL’inconvenient majeur de l’OPENMP est sa portabilite. En effet, elle est limitee aux

machines paralleles a memoire partagee.

Région séquentielle Région parallèle

Création de processus légers

Région séquentielle

Synchronisation des processus

Processus léger

Tâche maître

Fig. 2.2 – Fonctionnement d’OpenMP

Page 12: Mémoire final du projet TER Programmation performante d ...

Voici un exemple de code avec 3 taches executees dans la region parallele :

Listing 2.3 – Exemple de programme avec OpenMP

#i n c l u d e <s t d i o . h>#i n c l u d e <omp . h>

i n t main ( ){

i n t i = 5 ;i n t n = 9 ;i n t r e s = 0 ;

p r i n t f ( ” v a l e u r de i avant l a zone p a r a l l e l e : %d\n” , i ) ;p r i n t f ( ” v a l e u r de r e s avant l a zone p a r a l l e l e : %d\n” , r e s ) ;

//DEBUT REGION PARALLELE#pragma omp p a r a l l e l p r i v a t e ( i ) //On r e n t r e dans une zone

p a r a l l e l e avec i une v a r i a b l e p r i v e e#pragma omp f o r //On p a r a l l e l i s e l e f o r q u i s u i tf o r ( i =0; i <n ; i ++){

// omp get thread num ( ) r e c u p e r e l ’ i d du t h r e a dp r i n t f ( ” Thread %d e x e c u t e s l o o p i t e r a t i o n %d\n” ,

omp get thread num ( ) , i ) ;r e s +=1;

}// FIN REGION PARALLELE

p r i n t f ( ” v a l e u r de i a p r e s l a zone p a r a l l e l e : %d\n” , i ) ;p r i n t f ( ” v a l e u r de r e s a p r e s l a zone p a r a l l e l e : %d\n” , r e s ) ;

}

Listing 2.4 – Affichage

v a l e u r de i avant l a zone p a r a l l e l e : 5v a l e u r de r e s avant l a zone p a r a l l e l e : 0Thread 1 e x e c u t e s l o o p i t e r a t i o n 3Thread 1 e x e c u t e s l o o p i t e r a t i o n 4Thread 0 e x e c u t e s l o o p i t e r a t i o n 0Thread 0 e x e c u t e s l o o p i t e r a t i o n 1Thread 0 e x e c u t e s l o o p i t e r a t i o n 2Thread 2 e x e c u t e s l o o p i t e r a t i o n 6Thread 2 e x e c u t e s l o o p i t e r a t i o n 7Thread 2 e x e c u t e s l o o p i t e r a t i o n 8Thread 1 e x e c u t e s l o o p i t e r a t i o n 5v a l e u r de i a p r e s l a zone p a r a l l e l e : 5v a l e u r de r e s a p r e s l a zone p a r a l l e l e : 9

On voit bien dans cet exemple qu’apres la region parallele, la variable privee i a conserve

Page 13: Mémoire final du projet TER Programmation performante d ...

la valeur qu’elle avait avant la region parallele. Nous pouvons aussi observer que tous lesprocessus se sont bien partage les iterations de la boucle, en effet la valeur de res est bien celleattendue.

Page 14: Mémoire final du projet TER Programmation performante d ...

2.1.3 Cartes graphiques

Introduction

GPGPU pour General-Purpose computation on Graphics Processing Units est une techniquequi consiste a tirer parti des capacites de traitement des processeurs des cartes graphiques(GPUs) afin d’effectuer des calculs generaux habituellement realises par le CPU. Ces derniersetant adaptes au traitement des images, possedent donc une architecture faite pour le calculsur les images. En effet la plupart des transistors presents dans le GPUs sont dedies au calcul etnon au controle de flux, ou au entrees sorties comme sur les CPUs (Figure 2.3). Actuellementles GPUs sont utilises pour des applications beaucoup plus generales et dispose d’interface deprogrammation plus facile d’acces utilisant des langages de programmation standard comme leC. En portant une application sur GPU le developpeur doit prendre en compte l’architecture tresspecifique au traitement graphique mais peut esperer un gain de performance tres importantcompare a une implementation standard (de 20x a 150x selon le type d’application[2]).

Fig. 2.3 – Difference entre un CPU et un GPU

CUDA

CUDA est une architecture de traitement parallele developpee par Nvidia permettant dedecupler les performances de calcul du systeme en exploitant la puissance des GPUs Nvidia.CUDA est deja utilise par des milliers de developpeurs de logiciels, de scientifiques et de cher-cheurs qui l’utilisent dans une grande gamme de domaines, incluant notamment la productionvideo, l’astrophysique, la chimie et la biologie par modelisation numerique, la mecanique desfluides, les interferences electromagnetiques, la reconstruction tomodensitometrique, l’analysesismique, le ray tracing et bien plus encore. La programmation avec CUDA utilise en faitune surcouche du langage C et ne necessite donc pas de connaissances supplementaires pourdevelopper. Il faut cependant prendre en compte la particularite des GPUs qui consiste en unearchitecture massivement parallele (Figure 2.3).

Une nouvelle notion en CUDA est celle de kernel, qui designe une fonction qui sera executeepar le GPU. Un kernel est lance sur une grille de blocs, eux memes composes de threads(Figure2.4). Actuellement la grille ne peut avoir que deux dimensions tandis que les blocs peuvent enavoir 3. Un element a prendre en compte lors de l’ecriture du programme est le fait que les

Page 15: Mémoire final du projet TER Programmation performante d ...

blocs peuvent etre executes dans n’importe quel ordre (parallelement ou sequentiellement) etne doivent donc pas dependre des autres.

Afin de faciliter la parallelisation chaque thread dispose de variables globales permettantde connaitre le numero et la dimension du bloc sur lequel ils sont executes, et leur numerode thread. Ainsi une addition de matrice sera tres simple a ecrire (Listing 2.5). Une autreparticularite est la presence de differents niveaux de memoire sur les cartes graphiques. Parexemple l’acces a la memoire globale (de type DRAM) des cartes graphiques est beaucoup pluslong que l’acces a la memoire partagee entre les blocs. Toutes les specificites de l’architectureCUDA font qu’une mauvaise programmation peut aboutir a des temps de calculs jusqu’a 20fois plus long qu’une bonne implementation[1].

Listing 2.5 – Algorithme de multiplication de matrice avec CUDA

#i n c l u d e ” cuda . h”/∗ d e f i n i t i o n du k e r n e l ∗/

g l o b a l v o i d MatAdd ( f l o a t A [N ] [ N] , f l o a t B [N ] [ N] ,f l o a t C [N ] [ N ] ){

// i n d i c e x du t h r e a di n t i = t h r e a d I d x . x ;

// i n d i c e y du t h r e a di n t j = t h r e a d I d x . y ;C [ i ] [ j ] = A [ i ] [ j ] + B [ i ] [ j ] ;

}

i n t main ( ){

. . ./∗ nombre de b l o c k s ∗/dim3 dimBlock (N, N) ;/∗ i n v o c a t i o n du k e r n e l ∗/MatAdd<<<1, dimBlock>>>(A, B, C) ;

}

OpenCL

OpenCL pour Open Computing Language est la combinaison d’une API et d’un langage deprogrammation derive du C, propose comme un standard ouvert par le Khronos Group (a l’ori-gine d’openGL aussi). L’objectif d’OpenCL est de faciliter la programmation de l’intersectionnaissante entre le monde des CPU et des GPU, les premiers etant de plus en plus paralleles,les seconds etant de plus en plus programmables. Dans un premier temps, OpenCL permet-tra aux applications necessitant des calculs lourds de tirer parti de la puissance des circuitsgraphiques. Mais OpenCL vise aussi a tirer partie de CPU multi-cœurs, des multi-processeurstels le CELL d’IBM, qui equipe notamment la Playstation 3 de Sony, ou d’autres systemes decalcul intensifs.

Page 16: Mémoire final du projet TER Programmation performante d ...

KernelGrille

Bloc(0,0)

Bloc(1,0)

Bloc(0,1)

Bloc(1,1)

Thread(0,0,0)

Thread(1,0,0)

Thread(2,0,0)

Thread(2,1,0)

Thread(2,2,0)

Thread(1,1,0)

Thread(0,1,0)

Thread(1,2,0)

Thread(0,2,0)

Fig. 2.4 – Execution d’un kernel sur une grille de blocs

Par ses objectifs techniques, OpenCL se rapproche du C pour CUDA. Actuellement le projetOpenCL en est a ses debuts mais de nombreuses implementations pour la plupart des ma-chines existent deja. Cependant OpenCL est relativement jeune (premiere demonstration le10 decembre 2008 par AMD et NVIDIA au SIGGRAPH) et son API est sujette a evolution.De plus avant d’arriver a un resultat equivalent a celui d’un programme CUDA il faut ecrirebeaucoup plus de code. Notons cependant que le code ecrit pour OpenCL pourra etre reutilisepour tourner sur un CPU.

2.1.4 Processeurs programmables

Les processeurs programmables aussi appeles FPGAs pour field-programmable gate array(reseau de portes logiques programmables) sont des circuits integres logiques qui peuvent etrereprogrammes a volonte. Le principe de programmation de ces circuits consiste en fait a coderune fonction logicielle par une implementation materielle. Ces composants sont constituesd’un grand nombre de cellules logiques librement assemblables. Ces dernieres sont en faitconstituees d’une table de correspondance (LUT pour Look-Up-Table) et d’une bascule. Latable de correspondance sert a implementer des equations logiques (OR, AND, XOR, etc)allant de 4 a 6 entrees et possedant une sortie.

Comme dit precedemment les FPGAs possedent un grand nombre de blocs logiques qui sontconnecte entre eux par une matrice de routage configurable. Cette matrice occupe une grandeplace sur le silicium du composant et implique donc un prix eleve.

Les FPGAs se programment a l’aide d’un langage de description materiel. Les deux principauxlangages actuellement utilises sont le Verilog (melange description materielle et algorithmique)et le VHDL (VHSIC Hardware Description Language) qui est maintenant le plus repandu. Ilsert a decrire le comportement ainsi que l’architecture du FPGA. Son but est de faciliter ledeveloppement des circuits numeriques et pour cela on peut effectuer des simulations sansaucun materiel.

L’utilisation des FPGAs pour notre projet TER n’a pas ete envisageable car nous n’avionspas le materiel adequat a disposition.

Page 17: Mémoire final du projet TER Programmation performante d ...

2.2 Meta-programmation

2.2.1 La meta-programmation en general

La meta-programmation permet d’ecrire du code qui sera interprete non pas a l’execution,mais pendant la compilation. Par exemple, un programme peut changer son code pendant lacompilation en fonction de ses entrees.

La meta-programmation permet aussi d’effectuer des calculs mathematiques pendant lacompilation ce qui permet reduire le temps d’execution du programme. En effet, il a moinsde calculs a effectuer. En revanche, si une donnee manipulee par le meta-programme est uneentree du programme, alors elle ne peut pas etre connue avant son execution. Il est doncimpossible qu’un tel meta-programme soit interprete par un compilateur.

2.2.2 La meta-programmation en C++

Avant de compiler le programme, en C++, il est possible d’effectuer certaines modifica-tions sur le code source. Le programme effectuant ces modifications s’appelle le preprocesseur.Les commandes destinees au preprocesseur commencent toutes par # en debut de ligne.Il existe notamment les directives (#ifdef, #ifndef, #if, #endif et #else) quipermettent la compilation conditionnelle. C’est a dire que la partie du code comprise entreces directives n’est compilee que si la condition est remplie.

Voici un exemple :

Listing 2.6 – Exemple d’utilisation des directives du preprocesseur

#d e f i n e FRANCAIS 1#d e f i n e ANGLAIS 2#d e f i n e LANGUE FRANCAIS

i n t main ( ){#i f LANGUE == FRANCAIS

cout << ”BONJOUR” ;#e l i f LANGUE == ANGLAIS

cout << ”HELLO” ;#e n d i f

r e t u r n 0 ;}

Dans ce programme, il suffit de remplacer #define LANGUE FRANCAIS par#define LANGUE ANGLAIS et de recompiler le programme pour passer d’une versionfrancaise a une version anglaise. Ceci pourrait etre utile si le programme comportait denombreuses lignes.

En C++, il est aussi possible en utilisant des templates de manipuler des donnees generiqueset de mettre a contribution le compilateur pour generer le code final voulu. Grace a cettemethode, il est donc possible de creer des classes ou des fonctions generiques, c’est-a-dire des

Page 18: Mémoire final du projet TER Programmation performante d ...

classes ou des fonctions qui acceptent n’importe quel type du moment qu’il soit compatibleavec le code produit.Voici un exemple montrant le calcul de la factorielle d’un nombre :

Listing 2.7 – Exemple d’utilisation des templates

template< i n t n>s t r u c t F a c t o r i e l l e {

enum { r e s u l t a t=F a c t o r i e l l e <n−1>:: r e s u l t a t ∗n } ;} ;// Le t e m p l a t e s p e c i a l i s a t i o ntemplate<>s t r u c t F a c t o r i e l l e <0> {

enum{ r e s u l t a t =1};} ;i n t main ( ) {

cout << ”La f a c t o r i e l l e de 10 e s t ” << F a c t o r i e l l e <10>::r e s u l t a t <<e n d l ;

}

Dans cet exemple la factorielle de 10 est calculee. Le nombre que nous souhaitons manipulerne pourra pas etre entre par l’utilisateur lors de l’execution car c’est pendant la compilationque le calcul est effectue. C’est l’un des inconvenients de la meta-programmation.

2.2.3 Les avantages

La meta-programmation permet d’optimiser notre code, en effet, elle rend l’applicationplus facile a maintenir en supprimant la redondance de code. Ce qui nous permet en cas demise a jour, de ne pas modifier plusieurs sections.De plus, elle nous permet d’effectuer descalculs pendant la compilation afin d’eviter de les faire pendant l’execution du programme.Ce qui optimise les performances de l’application. La meta-programmation permet aussi degenerer du code en fonction de parametres connus a la compilation.

2.2.4 Les inconvenients

Les meta-programmes ne sont pas toujours simple a deboguer. En effet, il est impossibled’afficher du texte pendant la compilation. Par exemple, par habitude, pour deboguer unprogramme, on affiche la valeur de la variable qui est la cause de l’erreur pour essayer de lacorriger, or il nous est impossible de le faire pendant la compilation. La meta-programmationpeut rendre le code illisible a cause de sa syntaxe lourde. Enfin, le dernier inconvenient est letemps de compilation qui est plus eleve malgre que le temps d’execution soit quant lui plusrapide.

Page 19: Mémoire final du projet TER Programmation performante d ...

Chapitre 3

Analyse de l’existant

Actuellement les projets proposant une approche de programmation haut-niveau a l’aidede la meta-programmation pour les architectures paralleles sont peu nombreux. Parmi ceux quiexistent, on peut citer HMPP, fruit de la societe CAPS et NT2 developpe par le LASMEA etle CNRS.

3.1 HMPP

Le principe de HMPP consiste en un ensemble de directives actuellement disponibles pourles langages C et Fortran, permettant de decharger une partie des calculs sur des accelerateursmateriels (unites SSE et GPU pour le moment). Les directives placees dans le code permettentde designer soit des codelets (fonctions specialisees pour un accelerateur), soit des callsite(appel de codelets). Les fonctions precedees de la directive codelet seront generees par lecompilateur pour les architectures cible que l’on veut (actuellement le generateur de codeHMPP supporte SSE, CUDA (Nvidia), BROOK (AMD)). Les appels de codelet quant a euxdevront etre precedes de la directive callsite. HMPP propose aussi des codelets deja ecrits quisont optimises pour differentes cibles.

Afin de compiler et de lancer les applications codees avec des directives HMPP, CAPS fournitun compilateur et un environnement d’execution (runtime).

Listing 3.1 – Exemple de directives HMPP

/∗ D e f i n i t i o n de l a f o n c t i o n sgemm en t a n t que c o d e l e t ∗/#pragma hmpp sgemm c o d e l e t , a r g s [m; n ; k ; a l p h a ; b et a ; a ; b ] . i o=in , &#pragma hmpp sgemm a r g s [ c ] . i o=i n o u t , t a r g e t=CUDA:BROOKv o i d sgemm ( i n t m, i n t n , i n t k ,f l o a t a lpha , f l o a t a [m] [ k ] , f l o a t b [ k ] [ n ] ,f l o a t beta , f l o a t c [m] [ n ] ) ;

i n t main ( i n t argc , c h a r ∗∗ a r g v ) {. . ./∗ A l l o c a t e d e v i c e and memory ∗/#pragma hmpp sgemm a l l o c a t e , a r g s [ c ] . s i z e ={M,N}/∗ P r e f e t c h a l l data , a l p h a and be ta a r e c o n s t a n t ∗/#pragma hmpp sgemm advanced load , a r g s [ a l p h a ; b et a ; a ; b ; c ] , &

19

Page 20: Mémoire final du projet TER Programmation performante d ...

#pragma hmpp sgemm a r g s [m; n ; k ; n ; a l p h a ; b et a ] . c o n s t=t r u e. . ./∗ D e f i n i t i o n de l ’ a p p e l de l a c o d e l e t en t a n t que c a l l s i t e ∗/#pragma hmpp sgemm c a l l s i t e , a r g s [ a l p h a ; be ta ; a ; b ; c ] . a d v a n c e d l o a d=

t r u e , &#pragma hmpp sgemm a s y n c h r o n o u ssgemm( M, N, K, a lpha , t1 , t2 , beta , t3 ) ;/∗ a s y n c h r o n o u s e x e c u t i o n b a r r i e r ∗/#pragma hmpp sgemm s y n c h r o n i z e/∗ r e t r i e v e c output data ∗/#pragma hmpp sgemm d e l e g a t e d s t o r e , a r g s [ c ]/∗ r e l e a s e th e d e v i c e ∗/#pragma hmpp sgemm r e l e a s e. . .}

Les directives sont traduites par le compilateur HMPP qui va ensuite generer un programmeexecutable avec le runtime HMPP. Le runtime va ensuite lier le programme dynamiquementavec les librairies d’accelerations voulues (pour le moment compatible avec CUDA, CAL/IL(AMD) et OpenCL). L’avantage est de pouvoir changer de librairie d’acceleration sans recom-piler le programme.

La librairie HMPP limite neanmoins les possibilites d’accelerations en etant lie aux possibilitesde generation de code du compilateur HMPP.

3.2 NT2

Une autre solution restant neanmoins plus specifique que la solution HMPP est la librairieNT2 (Numerical Templte Toolbox) de generation automatique de code d’algebre lineaire pourarchitecture SMP (processeurs a memoire partagee). Cette libraire basee sur des templatesC++ permet de generer du code optimise pour SSE2 ou Altivec automatiquement (Listing3.2).

Listing 3.2 – Exemple de code avec la librairie NT2

#i n c l u d e <nt2 / nt2 . hh>#i n c l u d e <nt2 / fml . hh>u s i n g namespace nt2 ;u s i n g namespace nt2 : : fml ;

i n t main ( ){

matr ix<double> a ; // m a t r i x d e c l a r a t i o na = 2∗ ones ( 4 , 4 ) ; // a i s a 4 x4 m a t r i x f u l l o f 2a = ( a / 2 . 0 ) + randn ( 4 , 4 ) ; // a = a /2 + 4 x4 random e l e m e n t sa = s q r t ( cos ( a ) ) ; // s q u a r e r o o t and c o s i n u sa = c at h ( a , a ) ; // a = [ a | a ]

Page 21: Mémoire final du projet TER Programmation performante d ...

a = a∗ t r a n s ( a ) ; // m a t r i x p r o d u c ta = mul ( a , a ) ; // e l e m e n t w i s e p r o d u c tr e t u r n 0 ;

}

La solution que l’on cherche a proposer se veut beaucoup plus generale que les deuxprecedentes en proposant une approche de programmation a respecter par le developpeurplutot qu’une generation de code automatique comme HMPP ou NT2. Elle se rapproche ce-pendant de celles ci par l’utilisation de templates C++ comme NT2 et du developpement surdes accelerateurs materiels plus varies que pour HMPP.

Page 22: Mémoire final du projet TER Programmation performante d ...
Page 23: Mémoire final du projet TER Programmation performante d ...

Chapitre 4

Besoins non fonctionnels

Analysons a present les besoins correspondant a la manipulation de l’application, que cesoit en matiere de conception, de performance, d’implementation et d’extension.

4.1 Liste des besoins

Voici une presentation des differents besoins non fonctionnels.

1. Programmation haut niveau avec abstraction de l’architectureDescription : L’utilisateur ne devra pas se soucier de l’architecture de la machine qu’ilutilise.Justification : On parle de programmation haut niveau avec abstraction del’architecture car la methode de parallelisation sera choisie lors de la compilation del’application.

2. Utilisation du langage C++Description : L’application devra etre developpee en C++.Justification : Ce langage est tout a fait adapte au contexte car il convient bien atoutes les methodes de parallelisation que l’on a developpe et aussi a un certain domainede programmation que l’on a a traiter, la metaprogrammation.Elle s’obtient en C++ en utilisant des templates, c’est-a-dire l’utilisation d’un modelespecifique de classe et de fonction. On y introduit ainsi la notion de genericite quis’obtient a l’aide des templates.

3. Optimiser les performancesDescription : Le grand interet de la parallelisation de code est le gain de performancepar rapport a une execution sequentielle sur un seul processeur.Justification : Le fait d’optimiser au mieux les performances va de pair avec laparallelisation puisque que celle-ci existe avant tout pour reduire au maximum les tempsde calcul. Il est egalement interessant d’exploiter au mieux les ressources que l’on a anotre disposition.

4. Traiter differentes solutions de parallelisation existantesDescription : Le logiciel sera en mesure de tester plusieurs solutions de parallelisationpour un meme programme.

23

Page 24: Mémoire final du projet TER Programmation performante d ...

Justification : Le fait de traiter differentes solutions a pour but d’une part decomparer, de connaıtre les avantages et les inconvenients de chaque solution et d’autrepart d’avoir en sortie un binaire le plus efficace possible pour le programme en entree.

5. PortabiliteDescription : L’application devra gerer le fait que certaines solutions de parallelisations(SSE, CUDA, MPI et OpenMP) ne puissent pas tourner sur la machine en question.Justification : Notre application sera portable puisqu’elle verifiera si les librairieset le materiel necessaire au bon fonctionnement des methodes de parallelisations sontpresentes.

6. DocumentationDescription : Le projet sera livre avec une documentation decrivant les differentescontraintes que l’utilisateur devra respecter pour l’utilisation du programme.Justification : En effet, l’utilisateur devra respecter certaines conventions de pro-grammation des differentes architectures et des methodes d’utilisation de l’application.

4.2 Problemes techniques

Differents problemes sont apparus lors du developpement de programmes parallelisables,que ce soit sur la conception mais aussi au niveau des evaluations des performances.

• Combinaison de differents types de parallelismesDescription : Afin d’ameliorer les resultats d’execution, il peut etre interessant decumuler differents parallelismes. On peut par exemple, pour une machine possedant 2cœurs ayant chacun une unite vectorielle, combiner la repartition des calculs a la foissur les cœurs, tout en utilisant l’unite vectorielle de ces derniers.Cependant, chaque parallelisme possede ses contraintes (alignement des donnees enmemoire par exemple), on se retrouve donc confronte a des difficultes d’implementations.Solution : Nous avons effectue de nombreux tests preliminaires, en integrantdifferentes architectures comme par exemple SSE et OpenMP pour arriver au final aune implementation la plus optimisee possible.

• Utilisation du C++Description : Il a fallu apprendre le C++ pour demarrer l’implementation desdifferentes architectures et des templates, langage que l’on ne maitrisait pas totalement.Solution : Une phase d’apprentissage du langage C++ assez consequente a du etrefaite au detriment de la recherche de solutions a la problematique initiale.

• Precision des calculsDescription : L’utilisation du GPU pour effectuer des calculs arithmetiques n’estegalement pas sans problemes, car l’implementation faite par le constructeur de lacarte graphique ne respecte pas forcement les standards (en particulier le standard IEEE754[12]), on peut donc retrouver des differences de resultats pour un meme algorithme.Solution : Il n’y a pas de solution a ce probleme vu que nous sommes dependants duconstructeur.

Page 25: Mémoire final du projet TER Programmation performante d ...

Chapitre 5

Besoins fonctionnels

Nous allons presenter, sous forme d’une maquette (future execution), les differentes phasesdemandees pour l’application.

5.1 Prototype papier

Listing 5.1 – Apercu de l’utilisation du programme final

u t i l i s a t e u r @ m a c h i n e : ˜ $ . / o p t i m i z e −PATH nbody−−> V e r i f i c a t i o n des a r c h i t e c t u r e s . . .

[ SSE ] Trouve[ MPI ] Trouve[ OpenMP ] Trouve[CUDA] Pas t r o u v e

−−> C o m p i l a t i o n en c o u r s . . .−−> E v a l u a t i o n des p e r f o r m a n c e s en c o u r s . . .−−> B i n a i r e o p t i m i s e : nbody OpenMP−−> R e s u l t a t s : r e s u l t a t s . png

u t i l i s a t e u r @ m a c h i n e : ˜ $ l snbodyo p t i m i z er e s u l t a t s . pngu t i l i s a t e u r @ m a c h i n e : ˜ $

L’application optimize prend en parametre un repertoire contenant un programme ecrittel qu’il existe une specification de code pour plusieurs architectures paralleles connues. Elle vaensuite tester si la machine possede le materiel et librairies necessaires pour compiler le codespecifique.Les binaires generes (un par architecture) seront executes chacun leur tour pour pouvoir esti-mer leur performance. Un fichier texte au format GnuPlot sera construit, ce qui permettra al’application de generer un graphique avec les resultats de chaque binaire. Le plus performantsera ensuite designe, et les autres seront supprimes. La commande ls met donc en evidencele fait que le binaire le plus performant a ete genere par l’application, et que les resultats detous les binaires existants sont stockes sous forme d’un graphique dans un fichier PNG.

25

Page 26: Mémoire final du projet TER Programmation performante d ...

5.2 Liste des besoins

Nous allons a present detailler point par point les differentes fonctionnalites demandees.

1. Programme source en entreeDescription : Un programme source generique sur plusieurs implementations parallelessera soumis a l’application pour y etre optimise le plus possible sur l’architecture cible.Justification : Le programme doit etre generique car chaque implementationparallele possede un code bien specifique.

2. Programme source de type simulation ou moteur graphiqueDescription : Le programme source devra executer une partie importante de calculs.Justification : Si un programme ne contient que tres peu de calculs, il n’y a pasgrand interet a le paralleliser.

3. Verification des differentes architectures disponiblesDescription : L’application devra detecter si l’architecture de compilation est com-patible avec differents types de parallelismes implementes dans le programme fourniten entree. En plus de la presence de materiel supportant le parallelisme, il faudra parailleurs s’assurer que l’environnement de programmation est present (SDK pour laprogrammation GPU, bibliotheques/librairies ...).Justification : Il faut preparer la phase de compilation en s’assurant que la machinepossede bien les dependances necessaires.

4. Compilation des differents binairesDescription : Une fois l’enumeration faite, il suffira de compiler un binaire pourchaque architecture parallele presente. Ils seront compiles en utilisant les memes optionsde compilation, et sans interface graphique s’ils en possedent une.Justification : Tous les binaires compatibles avec la machine doivent etre compilesseparement pour pouvoir verifier leurs performances (hors visualisation).

5. Utilisation de plusieurs parallelismes simultanementDescription : Dans le cas ou plusieurs parallelismes sont presents, ils pourront etre”melanges” de facon a obtenir le meilleur profit.Justification : On peut optimiser l’utilisation du parallelisme en cumulant parexemple unites vectorielles et GPU.

6. Evaluation des performancesDescription : Chaque binaire sera execute a son tour avec le meme nombre de corpset sur differents nombres d’iterations. Le temps necessaire pour chaque essai sera gardeen memoire pour etre analyse par la suite.Justification : Pour pouvoir dire qu’un binaire est plus performant qu’un autre surune machine, il faut realiser plusieurs executions avec des entrees differentes.

7. Produire un fichier de resultatsDescription : Pour pouvoir comparer les differents types de parallelismes, un fichier desortie contenant les donnees necessaires a la realisation d’evaluations des performances

Page 27: Mémoire final du projet TER Programmation performante d ...

(nombre d’iterations en fonction du temps par exemple) sera cree au format GnuPlot.Justification : Dire qu’une architecture est moins performante qu’une autre ne noussatisfait pas pleinement, il est plus interessant de connaıtre l’ecart.

8. Binaire le plus optimiseDescription : Le binaire le plus performant integrera, par exemple, une visualisation3D du phenomene simule.Justification : Cette interface permettra de verifier la coherence des resultats obtenuslors des calculs de l’application de simulation.

5.3 Problemes techniques

Durant la phase de documentation, nous nous sommes apercu que certains pointsposeraient des problemes. Dans certains cas, ces soucis ont ete rencontres non pas durant laperiode de documentation, mais lors de la conception de prototypes.

• Verification de differentes architecturesDescription : Certaines architectures ne sont pas detectables directement en consul-tant le materiel (hardware) de la machine (cpuinfo par exemple). En effet, CUDA n’estcompatible qu’avec certains modeles de cartes graphiques, et il n’est pas envisageabled’avoir une liste ”complete”.Solution : Il y a la possibilite avec CUDA de detecter, si une carte graphique estcompatible avec le SDK.

• Evaluation des performancesDescription : Les resultats trouves n’ont pas toujours ete ceux attendus. Par exemple,une mauvaise utilisation des registres SSE a conduit a une degradation importantedes performances. Solution : Le code source a ete modifie de facon a mieux utili-ser les registres mis a notre disposition. De plus, les resultats dependent en partie del’implementation materielle qui est faite par le constructeur.

Page 28: Mémoire final du projet TER Programmation performante d ...
Page 29: Mémoire final du projet TER Programmation performante d ...

Chapitre 6

Premiere experimentation

6.1 Probleme des N-Corps

La simulation des N-Corps calcule une approximation numerique de l’evolution d’unsysteme compose de N corps, lesquels interagissent continuellement avec tous les autres. Unexemple d’application est la simulation astro-physique dans laquelle chaque corps representeune galaxie ou une etoile individuelle et chaque corps attire tous les autres a travers laforce gravitationnelle. Le repliement des proteines est etudie en utilisant la simulation desN-Corps pour calculer les forces de Van Der Walls. L’approche des ”all-pairs” pour lasimulation des N-corps est une technique de type ”brute-force” qui consiste a evaluer toutesles interactions corps a corps. C’est une methode relativement simple mais elle est rarementutilisee pour effectuer de grosses simulations car de complexite O(N2). En general, cetteapproche est utilisee pour calculer les forces dans les interactions de courte portee. Enpratique la methode des ”all-pairs” est combinee avec une methode basee sur l’approximationa longue portee des forces entre differents systemes a condition que ceux-ci soient bien separes.

Il existe en effet plusieurs implementations de cet algorithme, comme celle de Barnes-Hut,ou encore the Fast Multipole Method [4].

Ce probleme est particulierement interessant dans notre cas car il necessite une part impor-tante de calculs et est facilement parallelisable. Nous allons donc implementer ce programmesuivant differents types de parallelismes presentes auparavant, puis effectuer des evaluationsde performances pour verifier l’efficacite. Enfin, une interface graphique sera realisee (Figure6.1) pour s’assurer que le phenomene est correctement simule.

29

Page 30: Mémoire final du projet TER Programmation performante d ...

Fig. 6.1 – Apercu de l’interface realisee avec OpenGL et Glut

6.2 Implementations

Pour realiser les differents tests qui vont suivre, nous nous sommes bases sur la methode desimulation des ”all-pairs”. En effet, cette methode n’est bien entendu pas la plus performante,mais elle se parallelise assez facilement sur tous les types d’architectures.

6.2.1 SSE

L’interet du SSE reside dans la manipulation de vecteurs. Dans notre cas, il est doncessentiel de modeliser le probleme des N-Corps sous forme de vecteurs. La partie la pluscouteuse se situe au niveau du calcul de l’acceleration entre un corps et ses voisins. Etantdonne que ce calcul est base sur des operations arithmetiques classiques, on peut donc utiliserl’unite vectorielle du processeur pour effectuer 4 calculs d’accelerations simultanement. Unefois cela fait, le calcul des nouvelles positions puis la mise a jour des tableaux de coordonneespourront etre effectues avec 4 fois moins d’operations.

Listing 6.1 – Algorithme de calcul simplifie de l’acceleration entre les corps

Pour i de 1 a nbCorps F a i r ePour j de 1 a nbCorps par pas de 4 F a i r e

f l o a t acc [ 3 ] = {0 , 0 , 0} ;acc=b o d y B o d y I n t e r a c t i o n ( i , j ) ;t a b F o r c e [ AXIS X ] [ i ] += acc [ AXIS X ] ;t a b F o r c e [ AXIS Y ] [ i ] += acc [ AXIS Y ] ;t a b F o r c e [ AXIS Z ] [ i ] += acc [ AXIS Z ] ;

F inPourFinPour

Page 31: Mémoire final du projet TER Programmation performante d ...

F o n c t i o n i n t [ ] b o d y B o d y I n t e r a c t i o n ( i , j , acc )Debut

v1 = i +1;v2 = i +2;v3 = i +3;v4 = i +4;acc [ 0 ] = c a l c u l e r A c c e l e r a t i o n X ( i , v1 , v2 , v3 , v4 ) ; /∗ O( 1 ) ∗/acc [ 1 ] = c a l c u l e r A c c e l e r a t i o n Y ( i , v1 , v2 , v3 , v4 ) ; /∗ O( 1 ) ∗/acc [ 2 ] = c a l c u l e r A c c e l e r a t i o n Z ( i , v1 , v2 , v3 , v4 ) ; /∗ O( 1 ) ∗/R e t o u r n e r acc ;

F in

Cette fonction applique la formule du calcul de l’acceleration entre le corps i et 4 de sesvoisins, puis somme les 4 interactions (Figure 6.2). On a donc calcule 4 interactions avecseulement un seul appel a bodyBodyInteraction.

Corps i

Corps v1 Corps v2 Corps v3 Corps v4

Xmm0

Xmm1

A(i,v1)     A(i,v2) A(i,v3)    A(i,v4) acc[_]

calculerAcceleration_(i, v1, v2, v3, v4)

Corps i Corps i Corps i

+ + +

Fig. 6.2 – Illustration du calcul de l’acceleration entre 1 corps et 4 de ses voisins

Page 32: Mémoire final du projet TER Programmation performante d ...

6.2.2 OpenMP

En rajoutant les directives #pragma omp parallel private(i) et #pragma omp fordans le code suivant, nous avons parallelise le programme.

Listing 6.2 – Algorithme de calcul simplifie

i n t i ;#pragma omp p a r a l l e l p r i v a t e ( i )#pragma omp f o rf o r ( i = 0 ; i < numBodies ; ++i ) {

t a b F o r c e [ AXIS X ] [ i ] = t a b F o r c e [ AXIS Y ] [ i ] =t a b F o r c e [ AXIS Z ] [ i ] = 0 ;

f o r ( i n t j = 0 ; j < numBodies ; ++j ) {i f ( i != j ) {

f l o a t acc [ 3 ] = {0 , 0 , 0} ;b o d y B o d y I n t e r a c t i o n ( i , j , acc ) ;

t a b F o r c e [ AXIS X ] [ i ] += acc [ AXIS X ] ;t a b F o r c e [ AXIS Y ] [ i ] += acc [ AXIS Y ] ;t a b F o r c e [ AXIS Z ] [ i ] += acc [ AXIS Z ] ;

}}

}

Dans la boucle partagee, chaque tache n’execute qu’un sous ensemble des iterationsde la boucle. Toutes les taches se partagent donc les calculs se trouvant dans la fonctionbodyBodyInteration. La clause ”private” quant a elle permet de privatiser la variable i.C’est-a-dire que chaque tache possedera sa propre variable, elle n’est pas partagee. Il fautsavoir que cette variable est indefinie a l’entree de la region parallele et sa valeur n’est pastransmise a la region sequentielle qui suit.

Page 33: Mémoire final du projet TER Programmation performante d ...

6.2.3 MPI

L’implementation des N-Corps en MPI se revele tres interessante si l’on veut executerl’application sur une architecture regroupant plusieurs machines en parallele. C’est son principalinteret par rapport aux autres methodes de parallelisation qui ont ete traitees dans ce projet.

Cependant, l’architecture MPI est une bonne methode de parallelisation sur une seule ma-chine multi-processeurs comme le montre le schema 6.7 dans la partie ”Test de performance”.

Concretement, tous les processeurs vont se partager les corps, calculer leurs nouvelles coor-donnees puis propager celles-ci sur tous les autres processeurs par passage de messages.Le passage de messages entre les differents processus se fait grace a la fonctionMPI ALLGATHER[3]. Elle permet de rassembler tous les nouvelles coordonnees calculees etde les distribuer a tous les processus (Figure 6.3).

p1

p2

p3

p4

p1

p2

p3

p4

Fig. 6.3 – Schema du fonctionnement de MPI ALLGATHER

L’exemple ci-dessous montre une fonction de type accession pour le probleme des N-Corpspermettant au processus numerote 0 de recuperer toutes les vitesses des autres processus

Listing 6.3 – Rapatriement de donnees sur un seul processus

f l o a t NbodySimulat ion<OPENMPI> : : g e t V e l A r r a y ( ) {i f ( p i d == 0) {

MPI Gather ( t a b V e l [ AXIS X ] , s i z e , MPI FLOAT , b u f V e l [ AXIS X ] ,s i z e , MPI FLOAT , 0 , MPI COMM WORLD) ;

MPI Gather ( t a b V e l [ AXIS Y ] , s i z e , MPI FLOAT , b u f V e l [ AXIS Y ] ,s i z e , MPI FLOAT , 0 , MPI COMM WORLD) ;

MPI Gather ( t a b V e l [ AXIS Z ] , s i z e , MPI FLOAT , b u f V e l [ AXIS Z ] ,s i z e , MPI FLOAT , 0 , MPI COMM WORLD) ;

}r e t u r n b u f V e l ;

}

Page 34: Mémoire final du projet TER Programmation performante d ...

6.2.4 CUDA

La simulation du probleme des N-corps avec la methode des all-pairs est particulierementbien adaptee a la programmation avec CUDA. En effet l’architecture des GPU est massivementparallele et on va pouvoir adapter le probleme de facon a traiter le calcul d’attraction entrechaque corps de maniere quasiment simultanee pour une iteration. En effet la simulation desN-corps consiste a calculer la force entre chaque corps puis d’actualiser les coordonnees ducorps. Comme le calcul de force entre deux corps est repete un tres grand nombre de fois defacon independante, ce probleme est particulierement sujet a etre accelere sur GPU.

Un article publie par Nvidia[10] presente une implementation du probleme des N-corps avecCUDA. Son principe consiste a confier a chaque bloc un corps par thread. Cependant a causedes specificites de l’architecture des GPUs il faut prendre en compte les temps d’acces a lamemoire ainsi que la taille de la memoire partagee par bloc. Une premiere approche aurait puetre de charger tous les corps en memoire globale puis de calculer l’interaction par rapport achaque corps directement. Cependant l’acces a la memoire globale est tres lent sur les GPU.La solution consiste a allouer la memoire partagee en fonction de la taille des blocs puis derealiser le calcul d’attraction des corps en plusieurs fois pour chaque bloc.

Dans un premier temps on copie une partie des corps (egale a la taille des blocs) dans lamemoire partagee puis chaque thread calcule la force d’attraction de son corps par rapport aceux charges dans la memoire partagee. On reitere cette etape jusqu’a avoir calcule l’interactionavec tous les corps comme le montre le listing 6.4.

Listing 6.4 – Calcul de l’acceleration pour un thread

d e v i c e f l o a t 3 computeBodyAccel ( f l o a t 4 bodyPos , f l o a t 4 ∗ p o s i t i o n s, i n t numBodies ) {e x t e r n s h a r e d f l o a t 4 sh ar edP os [ ] ;// v e c t e u r d ’ a c c e l e r a t i o nf l o a t 3 acc = {0 . 0 f , 0 . 0 f , 0 . 0 f } ;// nombres de t i l e s t o t a l ( p a r t i e de 256 c o r p s )i n t numTi les = numBodies / mul24 ( blockDim . x , blockDim . y ) ;// b o u c l e p r i n c i p a l ef o r ( i n t t i l e = b l o c k I d x . y ; t i l e < numTi les + b l o c k I d x . y ; t i l e

++) {// a chaque t o u r de b o u c l e chaque t h r e a d c o p i e un c o r p s dans

l a memoire p a r t a g e esh ar ed Po s [ t h r e a d I d x . x+ mul24 ( blockDim . x , t h r e a d I d x . y ) ] =

p o s i t i o n s [ mul24 (WRAP( b l o c k I d x . x + t i l e , gr idDim . x ) ,blockDim . x ) + t h r e a d I d x . x ] ;

// syncs y n c t h r e a d s ( ) ;

// on c a l c u l e l e s i n t e r a c t i o n s avec t o u s l e s c o r p s que l ’ onv i e n t de c h a r g e r e t on s y n c h r o n i s e

acc = g r a v i t a t i o n ( bodyPos , acc ) ;s y n c t h r e a d s ( ) ;

}r e t u r n acc ;

}

Page 35: Mémoire final du projet TER Programmation performante d ...

Afin d’optimiser les performances, la multiplication sur 24 bits est utilisee pour toutes lesmultiplications qui ne provoqueront pas d’overflow. En effet comme il est dit dans le manuel deprogrammation Nvidia [12] la multiplication sur 24 bit prend moins de cycle d’horloge que cellesur 32 bits (attention toutefois dans les prochaines architectures elle sera moins performante).Dans le calcul d’interactions on notera aussi la non-utilisation de la division flottante car celle-ciprend plus de cycle qu’une multiplication par l’inverse de la valeur.

6.3 Evaluation des performances

6.3.1 Protocole

Pour realiser les evaluations des performances presentes ci-dessous, nous avons utilise lamachine Opteron-Cuda mise a notre disposition. Elle possede 2 processeurs Quad-Core AMDOpteron 2376 (ce qui fait un total de 8 cœurs) et une carte graphique Nvidia GT200. Nousavons lance diverses implementations du programme des N-Corps et fait varier le nombred’iterations 100 a 10 000.

Les modalites d’evaluations ont ete differentes avec CUDA, car le nombre de corps doit etreassez important pour etre interessant. Les options de compilations -O3 ont ete utilisees.

6.3.2 Resultats

CPU vs SSE

100 500 1000 5000 10000

0

10

20

30

40

50

60

Comparaison  entre les implémentations classique et SSEOpteron­Cuda

CPU

SSE

Nombre d'itérations

Tem

ps e

n se

cond

e

Fig. 6.4 – Evalution des performances entre les versions sequentielle et SSE

Page 36: Mémoire final du projet TER Programmation performante d ...

CPU vs OpenMP

100 500 1000 5000 10000

0

10

20

30

40

50

60

Problème des N­Corps implémenté avec OpenMPOpteron­Cuda

CPU

OpenMP 2

OpenMP 4

OpenMP 8

Nombre d'itérations

Tem

ps e

n se

cond

e

Fig. 6.5 – Evalution des performances entre les versions sequentielle et OpenMP

OpenMP vs OpenMP+SSE

100 500 1000 5000 10000

0

5

10

15

20

25

30

35

Comparaison entre les implémentations OpenMP et SSE+OpenMPOpteron­Cuda

OpenMP 2

OpenMP 4

OpenMP 8

SSE+OpenMP 2

SSE+OpenMP 4

SSE+OpenMP 8

Nombre d'itérations

Tem

ps e

n se

cond

e

Fig. 6.6 – Evalution des performances entre les versions OpenMP et OpenMP+SSE

Page 37: Mémoire final du projet TER Programmation performante d ...

CPU vs MPI

100 500 1000 5000 10000

0

10

20

30

40

50

60

Problème des N­Corps sur implémentation MPI

Opteron­Cuda

CPU

MPI 2

MPI 4

MPI 8

Nombre d'itérations

Tem

ps e

n se

cond

e

Fig. 6.7 – Evalution des performances entre les versions sequentielle et MPI

CPU vs CUDA

256 512 1024 2048

0

5000

10000

15000

20000

25000

30000

35000

40000

temps de calcul en ms sur 100 iterations

CPUCUDA

nombre de corps

tem

ps

en

ms

Fig. 6.8 – Evalution des performances entre la version CPU et CUDA

Page 38: Mémoire final du projet TER Programmation performante d ...

6.3.3 Interpretations

On peut donc constater que la version SSE apporte un gain d’environ 2 par rapport a laversion sequentielle normale, tout comme la version OpenMP sur 2 processeurs. Ensuite, onremarque que la version OpenMP + SSE apporte a nouveau un gain d’environ 2 par rapporta une version OpenMP avec le meme nombre de cœurs. On remarque que pour 8 cœurs, lefacteur d’acceleration est moins important que pour 4 et 2, ce qui s’explique par le fait qu’iln’y a qu’un seul bus memoire pour les 2 processeurs, et qu’il ne peut pas etre utilise par les2 processeurs simultanement. On peut noter que le gain obtenu avec SSE depend en grandepartie de l’implementation materielle qui est faite par le constructeur. Le meme code a obtenuun gain d’environ 3.5 sur un processeur Intel T1600.

En ce qui concerne la version MPI, on s’apercoit que le gain gagne jusqu’a 1000 iterationsn’est pas tres important car sur de petites valeurs, le temps d’envoi de messages entre lesdifferents cœurs est presque autant important que le calcul des coordonnees. Par contre, pourdes valeurs plus elevees, on remarque bien que le temps d’execution en MPI est nettementinferieur a celui de la version sequentielle et qu’il est proportionnel aux nombres de cœursutilises.

Pour comparer les performances de CUDA, les tests ont ete effectues avec un nombre decorps de plus en plus grand. L’implementation en cuda n’est vraiment efficace (utilise toutesles performances de la carte graphique) qu’a partir de 2048 corps. En dessous de ce nombre, lesmultiprocesseurs du GPU sont sous-utilises et le temps de transfert des donnees est nettementsuperieur au temps de calcul. Cependant, compare a l’implementation CPU, le speed-up estdeja tres important et est de l’ordre de 100 pour 2048 corps. Si on avait continue les testsen augmentant le nombre de corps de la version CPU, il aurait continue a croıtre de faconquadratique, tandis que la version CUDA aurait augmentee de facon beaucoup plus moderee.

Page 39: Mémoire final du projet TER Programmation performante d ...

Chapitre 7

Exemples de fonctionnement

L’objectif de ce projet est de concevoir une application (optimize) permettant, pour unprogramme fourni en parametre, de determiner les performances des differents parallelismesimplementes sur l’architecture cible afin d’en selectionner le meilleur. Nous avons donc realisedeux programmes (dont le probleme des N-Corps vu precedemment) afin de tester l’application.Les details concernant l’architecture du programme passe en parametre seront detailles dansle chapitre ”Commentaires techniques”

Le but de ce programme n’est pas d’en tirer les meilleurs performances (algorithme encomplexite lineaire), mais de bien comprendre la structure que les programmes manipules parle script optimize doivent avoir en entree.

Nous avons realise une classe generique permettant d’additionner 2 vecteurs suivantdifferents types de parallelisme (SSE, OpenMP, MPI, Cuda) en utilisant des principes issusde la meta-programmation (specialisation de templates, directives ifdef ...).

7.1 Algorithmes

Afin de ne pas avoir de difficultes pour comprendre le code, ce sont des algorithmes lesplus simples possibles qui ont ete choisis pour additionner les vecteurs.• Sequentiel : Chaque composante des vecteurs est additionnee une a une (Listing 14.1).• SSE : Les composantes des vecteurs sont additionnees 4 par 4 (Listing 14.2).• OpenMP : Le vecteur est separe en n parties qui sont additionnees sequentiellement par

n threads (Listing 14.3).• OpenMPI : Idem que pour OpenMP, sauf que les n parties du vecteurs sont additionnees

sequentiellement par n processus (Listing 14.5).• CUDA : L’addition des vecteurs de taille n est lancee en parallele par n threads (Listing

14.4). Cela signifie que chaque thread additionne une composante des vecteurs.

7.2 Lancement du programme principal

u t i l i s a t e u r @ m a c h i n e : ˜ $ . / o p t i m i z e vectorAdd /V e r i f i c a t i o n s de base en c o u r s . . .V e r i f i c a t i o n s des a r c h i t e c t u r e s d i s p o n i b l e s . . .

39

Page 40: Mémoire final du projet TER Programmation performante d ...

A r c h i t e c t u r e s d i s p o n i b l e s dans l e m a k e f i l e : CPU OPENMP SSE SSEMPCUDA OPENMPI

A r c h i t e c t u r e s s u p p o r t e e s par l a machine :CPU s u p p o r t eOPENMP s u p p o r t eSSE s u p p o r t eSSEMP s u p p o r t eCUDA s u p p o r t eOPENMPI s u p p o r t eC o m p i l a t i o n des a r c h i t e c t u r e s d i s p o n i b l e s . . . CPU OPENMP SSE SSEMP

CUDA OPENMPILancement des t e s t s . . . CPU OPENMP SSE SSEMP CUDA OPENMPILa m e i l l e u r e a r c h i t e c t u r e e s t CUDAG e n e r a t i o n du g r a p h i q u e . . .u t i l i s a t e u r @ m a c h i n e : ˜ $

On voit sur le graphique genere (Figure 7.1) que toutes les implementations sont a peupres equivalentes excepte pour CUDA qui sort nettement du lot. En effet vu que cet exempleconsiste uniquement a montrer que le script optimize peut gerer plusieurs types de programmes,nous n’avons pas cherche a optimiser les algorithmes. Cependant de par la nature de CUDAle traitement reste tout de meme beaucoup plus rapide.

0

0.04

0.08

0.12

0.16

0.2

0.24

0.28

0.32

0.36

0.4

0.44

0.48

0.52

0 100000 200000 300000 400000 500000 600000 700000 800000 900000 1e+06

Tem

ps

en s

eco

nd

es

Taille des vecteurs

Graphique des performances

CPUOPENMP

SSESSEMPCUDA

OPENMPI

Fig. 7.1 – Courbe de performance des differentes implementations

Page 41: Mémoire final du projet TER Programmation performante d ...

Chapitre 8

L’architecture

8.1 Architecture du logiciel

Le logiciel (Figure 8.1) est compose d’un dossier Scripts qui contient tous les scripts bashpermettant notamment de verifier si une architecture est disponible sur la machine. Ces scriptsdispo.sh sont classes par architecture. Il y a aussi la presence de Basiques.sh permettantde verifier si gcc et gnuplot sont bien installes ainsi que Vars.sh affectant a une variable unecouleur, ce qui est pratique pour mettre de la couleur dans la console. Enfin, il y a un scriptGnuplot.sh permettant de generer un graphique des performances pour chaque architecture.

Le projet contient aussi un dossier contenant les sources du programmes que nous detailleronsplus tard ainsi qu’un script optimize.sh (Partie 9.2) permettant de lancer le programme.

Programme en entrée

Scripts

archisBasiques.sh Gnuplot.sh Vars.sh

CPU SSE OPENMP OPENSSE CUDA OPENMPI

optimize.sh

dispo.sh dispo.sh dispo.sh dispo.sh dispo.sh dispo.sh

PROJET

Légende

Fichier statique

Dossier

Fig. 8.1 – Architecture du logiciel

41

Page 42: Mémoire final du projet TER Programmation performante d ...

8.2 Structure du programme a evaluer

Le dossier contenant la structure du programme a evaluer (Figure 8.2) est compose d’unrepertoire src contenant les sources du programme ainsi qu’un dossier include contenantquant a lui les headers.

Ce dossier (Figure 8.2) contient aussi le Makefile que l’on detaillera plus tard (Partie 9.1.1)ainsi qu’un dossier data contenant le fichier gluplot.gp qui contient toutes les informationsdu graphique qui va etre genere, comme par exemple, le nom de l’axe des abscisses, la taillemaximale de l’axe des ordonnees, la couleur ou la forme des courbes. Ce dossier contient aussides fichiers .dat (Partie 9.1.2)(un pour chaque architecture) qui sont generes pendantl’execution des programmes puis deplaces et renommes par le script optimize. Ces fichierscontiennent les temps d’execution du programme en fonction d’un parametre comme parexemple le nombre de corps pour le probleme des N-Corps. Enfin, un dossier bin est genere achaque execution contenant les binaires pour chaque implementation.

Programme en entrée

data includes src graphique.png Makefile

gluplot.gp CPU.dat

OPENMP.dat SSE.dat

SSEMP.dat

OPENMPI.dat

CUDA.dat

Légende

Fichier statique

Fichier généré

Dossier

bin

Fig. 8.2 – Structure du programme a evaluer

Les sources et headers (Figure 8.3)

Le fichier simulation.h se charge d’inclure tous les fichiers .h et .tpp en fonction del’architecture. Le fichier definitions.h quant a lui affecte a toutes les macros du programmeune valeur. Comme par exemple la masse maximum d’un corps.

Les fichier d’extension .tpp sont les templates. Ils contiennent le code source du pro-gramme en entree. On peut s’apercevoir que CUDA, MPI et les autres architectures possedent

Page 43: Mémoire final du projet TER Programmation performante d ...

chacun leur propre template (Partie 9.1.2). En effet, ils n’ont pas la meme structure dedonnees, et sont donc un specialisation du template classique.Les fichiers d’extension .cucontiennent une partie des fonctions ecrites en CUDA qui vont etre utilisees par le templatenbodysimulationcuda.tpp. Cela est du au fait que les kernels CUDA sont en C et sont doncsepares du code objet.

Enfin, il y a 2 fichiers main :• Un main_plot.cpp qui permet d’effectuer les evaluations des performance des

differentes implementations en desactivant eventuellement certaines fonctionnalitescouteuses (operations d’entree/sortie par exemple).• Un main_visu.cpp qui va quant a lui contenir l’integralite des fonctionnalites. Par

exemple, pour le cas des N-corps, le main_visu.cpp va lancer la visualisation enOpenGL. Ce fichier va utiliser les classes Vector3D.cpp et CameraFreeFly.cpp.

srcincludes

CameraFreeFly.h

Vector3D.h

definitions.h

simulation.h

nbodysimulation.h

nbodysimulationcuda.h

nbodysimulationmpi.h

main_plot.cpp

main_visu.cpp

nbodysimulation.tpp

nbodysimulationcuda.tpp

nbodysimulationmpi.tpp

Vector3D.cpp

CameraFreeFly.cpp

nbodysimulationcuda.cu

nbodykernel.cu

Visualisation

Algorithme des NCorps

Visualisation

Programmes principaux

Algorithme des Ncorps

Légende

Fichier statique

Dossier

Fig. 8.3 – Les sources et headers du programme des N-Corps

Page 44: Mémoire final du projet TER Programmation performante d ...
Page 45: Mémoire final du projet TER Programmation performante d ...

Chapitre 9

Commentaires techniques

Nous allons distinguer d’une part les programmes fournis en parametre, qui doivent res-pecter quelques regles pour etre utilises, et d’autre part, le script Optimize.

9.1 Structure d’un programme en entree

9.1.1 Makefile

Afin que le script optimize.sh fonctionne correctement, le dossier passe en parametre doitcontenir un programme qui respecte une certaine structure. Dans un premier temps un fichierMakefile doit se trouver a la base du repertoire. Ce Makefile doit contenir plusieurs regles quisont les suivantes :

• plot : cette regle permet de generer un programme dans lequel la visualisation estdesactivee afin de realiser des evaluation des performances.• visu : permettra de generer l’executable optimal avec la visualisation (si elle existe).• args : permet de recuperer les arguments qui seront passes aux programmes de tests lors

de leur lancement.• info : renverra les architectures supportees par le Makefile et par consequent par le

programme.• axis x, axis y : affichera les noms des etiquettes x et y pour la generation du graphique

de performances.

Afin de specifier l’architecture on passera au Makefile l’argument ARCHI=ARCHI VOULUE.Le passage des parametres par l’option -D de gcc sera laisse au developpeur (voir Makefile del’exemple vectorAdd). Voici un exemple de Makefile :

SUPPORTED ARCHI=CPU OPENMP SSE SSEMP CUDA OPENMPIARGS=− i t e r 100 −donnees 50000 100000 250000 500000 750000 1000000AXIS X=T a i l l e des v e c t e u r sAXIS Y=Temps en s e c o n d e s. . .

META=−DARCHI=$ (ARCHI). . .p l o t : dependi f e q ( ”$ (ARCHI) ” , ”CUDA” )

45

Page 46: Mémoire final du projet TER Programmation performante d ...

make p l o t c u d ae l s e i f e q ( ”$ (ARCHI) ” , ”OPENMPI” )

make p l o t m p ie l s e

make plot commone n d i f. . .i n f o :

@echo $ (SUPPORTED ARCHI)a r g s :

@echo $ (ARGS)a x i s x :

@echo $ ( AXIS X )a x i s y :

@echo $ ( AXIS Y )

Sur cet exemple on voit que la regle plot rappelle differentes regles en fonction desimplementations. Ici comme les versions Mpi et Cuda utilisent des compilateurs specifiques(mpicc et nvcc), une differenciation est faite afin de lancer les bonnes regles de compila-tion. On voit aussi la variable META qui sera passee en parametre aux compilateurs afin d’etrerecuperee dans les programmes. Apres il suffit d’utiliser le #define ARCHI afin d’instancier unobjet avec l’implementation voulue.

Listing 9.1 – Exemple d’instanciation

. . .NbodySimulat ion<ARCHI> ∗ ns ;. . .

9.1.2 Programme

Le programme d’evalution des performances doit permettre de generer un fichier de donnees”data.dat” qui sera ensuite exploite par GnuPlot. La structure de ce fichier est de la formesuivante :

(donnee axe x) (donnee axe y)32 0.00025764 0.001029128 0.004211256 0.016643512 0.066412768 0.1495961024 0.265611536 0.5978712048 1.06157

Ce fichier doit etre genere dans le repertoire courant et se doit se nommer ”data.dat”(voirsrc/main plot.cpp dans le dossier vectorAdd). GnuPlot sera ensuite utilise afin de generer

Page 47: Mémoire final du projet TER Programmation performante d ...

automatiquement un graphique de comparaison des performances. Typiquement, les donneesde l’axe x sont celles passees en parametre (et recuperees via make args), et la colonne y estle temps necessaire pour la valeur x associee.

Dans le cas ou le programme dispose d’une interface graphique, on procedera de lameme facon pour compiler le programme avec une architecture en parametre (make visuARCHI=OPENMP par exemple).

Les differentes parties de codes specifiques aux architectures sont ecrites dans desspecialisations de templates lorsqu’elles sont reellement differentes, ou alors dans une methodegenerique via un parametre qui permet de garder une partie commune de code dans la methode.On choisira d’inclure une specialisation de template via une directive #ifdef. En effet pour MPIet nvidia des headers speciaux sont requis, le probleme est que si on les inclus conditionnelle-ment a la compilation, le compilateur verifie tout le code, meme les parties qui ne seront pascompilees (ie appartenant a une autre implementation). Ainsi, si l’utilisateur veut compiler laversion SSE, le compilateur verifiera le code pour Cuda quand meme, cependant comme lesheaders sont inclus avec un #ifdef, le compilateur ne trouvera pas les fonctions et la compila-tion echouera. A l’inverse si on inclus les headers directement, certains utilisateurs ne les aurontpas et la compilation echouera aussi. La solution a donc consiste a faire des specialisationsavec des #ifdef en dehors des implementations. Par exemple, pour l’addition de vecteurs, ona le fichier simulation.h suivant :

#i n c l u d e ” d e f i n i t i o n s . h”#i n c l u d e <memory . h>#i f ARCHI == CUDA

#i n c l u d e ” c u d a r u n t i m e . h”#i n c l u d e < i n t t y p e s . h>#i n c l u d e <math . h>#i n c l u d e ” n b o d y s i m u l a t i o n c u d a . tpp ”

#e l i f ARCHI == OPENMPI#i n c l u d e ”mpi . h”#i n c l u d e ” n b o d y s i m u l a t i o n m p i . tpp ”

#e l s e#i n c l u d e <omp . h>#i n c l u d e <pmmintr in . h>#i n c l u d e ” n b o d y s i m u l a t i o n . tpp ”

#e n d i f

Puis dans chaque fichier .tpp on aura les specialisations. On inclura ensuite le fichiersimulation.h dans le programme que l’on souhaite utiliser et on compilera avec le #defineARCHI en fonction de l’architecture voulue.

9.2 Le script Optimize

Pour lancer le programme, on execute la commande suivante : ./optimize.sh DossierCon-tenantLeProgramme

Page 48: Mémoire final du projet TER Programmation performante d ...

• VerificationsLe script commence par verifier si on a bien passe un repertoire en parametre. Si c’estle cas, il verifie ensuite si ce repertoire existe bien ainsi que la presence d’un Makefiledans celui-ci. On verifie ensuite que certaines dependances sont bien installees (utilitairede generation de graphique gnuPlot, compilateur g++ ...). Nous avons vu que dansle makefile se trouvait la liste des architectures supportees par le programme. Nousallons donc creer une liste d’architectures a l’aide de la commande make info. Nousallons ensuite parcourir cette liste et verifier pour chacune des architectures si elle estsupportee par la machine. Si c’est le cas, on la garde dans la liste, sinon on la retire.

• CompilationOn parcourt la liste des architectures supportees par le programme et la machine, etpour chacune d’entre elles, on compile le fichier main plot.cpp qui va permettre de fairedes evaluations des performances (make plot ARCHI=$ARCHI). Les binaires generesseront alors places dans le dossier bin du programme.

• ExecutionOn parcourt la liste des architectures dont le bin a ete genere, c’est-a-dire les archi-tectures ou la compilation s’est deroulee sans erreurs. Pour chacune d’entre elles, onexecute le programme en lancant le bin. Pendant l’execution, on recupere les tempsd’execution que l’on va stocker dans des fichiers .dat qui se trouvent dans le dossierdata. Chaque architecture possedera son propre fichier dat (par exemple CPU.dat pourl’architecture CPU).

• AnalyseOn analyse ensuite les fichiers de donnees afin de determiner l’architecture la plusperformante. On en profite aussi pour recuperer le temps le plus eleve afin d’etablir unevaleur maximale de l’axe des ordonnees pour le graphique.

• Resultat des performancesOn affiche l’architecture la plus performante pour ce programme.

• Preparation du graphiqueOn prepare le graphique en modifiant le fichier gnuplot.gp. Par exemple on definit lataille des axes en fonction des donnees, et on nomme les axes.

• Generation du graphiqueOn genere maintenant le graphique en fonction des donnees contenues dans le dossierdata.

• SuppressionOn supprime le dossier data.

Page 49: Mémoire final du projet TER Programmation performante d ...

Chapitre 10

Analyse en complexite

La complexite du programme principal est dependante de celle du programme passeen entree. En effet, optimize lance, apres avoir effectue quelques operations detailleesprecedemment (verifications, compilations ...), differentes executions du programme en entreepour realiser le benchmark.

Les etapes de verification des architectures, de compilation, de recherche de l’architecturela plus performante et de generation du graphique ont une complexite temporelle et spatialelineaire au nombre d’architectures soumises par le developpeur du programme fourni en entree.

L’etape la plus couteuse est celle concernant la realisation de benchmarks. Dans ce cas, lacomplexite temporelle est celle du programme en entree, multipliee par le nombre d’architec-tures proposees et supportees. La complexite spatiale ne depend que du programme source.

La partie concernant les verifications de bases (presence d’un compilateur par exemple) aune complexite constante en temps et en espace.

Le temps d’execution du programme optimize dependra des donnees passees en parametrepour effectuer les benchmarks sur le programme dont on souhaite connaıtre l’architecture laplus performante. Concretement, nous avons realise quelques mesures concernant les tempsd’execution des differentes parties sur des programmes en entree ayant des complexitesdifferentes (voir Figure 10.1).

Exemples Addition de vecteurs N-Corps

Temps de verification des architectures 0,34 0,33Temps de compilation 6 7,3Temps des tests de performances 8,9 7,9Temps de generation du graphique 0,07 0,09Temps d’execution globale 15,5 19,5

Fig. 10.1 – Resultats des differents temps d’execution (en secondes)

On remarque que les temps de verification des architectures et de generation du graphiquesont presque negligeables. Ce sont les temps de compilation et d’execution qui sont les plus

49

Page 50: Mémoire final du projet TER Programmation performante d ...

importants. Cela reste tout de meme acceptable, il ne faut pas que la duree d’execution duscript soit trop longue pour l’utilisateur. Le choix des valeurs pour la realisation des benchsdoit etre raisonnable.

Ces tests ont ete effectues sur un PC portable Intel Core 2 Duo 2,10 Ghz dote d’unecarte graphique Nvidia 8400M (il supporte toutes les architectures). Les donnees passees enparametre aux programmes exemples sont presentees dans le tableau suivant (Figure 10.2).

Ces parametres representent d’une part une liste de donnees a tester, et d’autre part lenombre d’iterations a effectuer pour une donnee. La liste de donnees pour l’addition de vecteursrepresente une liste de tailles de vecteur. Celle concernant le probleme des N-Corps representele nombre de corps. Concretement, la liste de donnees correspond aux differentes tailles duprobleme a evaluer.

Exemples Addition de vecteurs N-Corps

-donnees 50000 100000 250000 500000 32 64 128 256 512750000 1000000 768 1024 1536 2048

-iter 100 10

Fig. 10.2 – Donnees passees en parametre pour la realisation du test

On peut preciser que l’addition de vecteurs a une complexite spatiale et temporelle lineaireen la taille des vecteurs. La complexite du probleme des N-Corps a deja ete detaillee lorsde la partie ”Premieres experimentations”, on rappelle qu’elle est quadratique pour la partietemporelle, et lineaire concernant la partie spatiale.

Page 51: Mémoire final du projet TER Programmation performante d ...

Chapitre 11

Resultat des tests de validation et defonctionnement

Parmi les besoins de l’application, nous avons retenu neuf tests pertinents.

1. Optimiser les performancesTest realise : Sur l’exemple des N-Corps, nous pouvons conclure que nous avonsoptimise les performances puisque les gains obtenus pour toutes les solutions deparallelisation traitees s’approchent veritablement des gains que l’on aurait du avoir entheorie. Les autres exemples ne sont pas assez significatifs concernant l’optimisation desperformances puisqu’ils ne necessitent pas assez de calcul.

2. PortabiliteTest realise : Le logiciel a ete lance sur des machines avec comme systeme d’exploi-tation Ubuntu, allant de 2 a 8 cœurs comme le montre la figure 6.5 ou il fut utilise 2,4, et 8 cœurs pour effectuer ce test.

3. Programme source de type simulation ou moteur graphiqueTest realise : Nous avons constate qu’un programme demandant une quantitede calcul importante (N-Corps) est plus avantageux a etre parallelise plutot qu’unprogramme comme vectorAdd (addition de vecteur) qui en necessite beaucoup moins.Toutefois, en principe, n’importe quel type de programme peut etre lance par le logiciel,la seule difference est qu’il se peut que la version la plus performante ne soit pas uneversion parallelisee mais tout simplement sequentielle si le programme ne necessitequasiment pas de calcul. On notera egalement que le programme supporte d’autrestypes de programmes, comme ceux concernant le traitement d’image (voir l’exempleavec le filtre de Sobel).

4. Verification des differentes architectures disponiblesTest realise : Le script Optimize execute les differents fichiers dispo.sh de chaquearchitecture pour creer en definitif une liste d’architectures supportees par la machineet ainsi compiler et tester ces differentes methodes de parallelisation (Partie 7.2). Ainsi,sur une machine ne disposant pas de la technologie CUDA, le script ne compilera pascette version.

51

Page 52: Mémoire final du projet TER Programmation performante d ...

5. Compilation des differents binairesTest realise : Le script Optimize appelle le Makefile du programme en entree pourchaque architecture qui se trouve dans la liste des architectures supportees. Ces appelsconduisent a la compilation de toutes ces architectures car le graphique de performanceest bien genere avec les courbes des differentes architectures.

6. Utilisation de plusieurs parallelismes simultanementTest realise : Il se peut que dans certains cas, il soit judicieux de cumuler plusieursmethodes de parallelisation pour obtenir un binaire plus performant que ceux desdifferentes methodes utilisees. Ce fut le cas sur le probleme des N-Corps, ou on obtientun gain plus important en cumulant SSE et OpenMP par rapport aux gains de ces deuxmethodes utilisees separement (Figure 6.6).

7. Tests de performanceTest realise : Le nombre de tests pour garantir que le binaire en sortie est le plusperformant est connu dans le Makefile de chaque programme. Une liste d’arguments estainsi listee par l’utilisateur et les resultats de tous ces tests sont bien enregistres dansdes fichiers de donnees.

8. Produire un fichier de resultatsTest realise : Le script Optimize genere bien un fichier de resultats par architecturecar le graphique de performance est genere d’apres ces fichiers.

9. Binaire le plus optimiseTest realise : D’apres la meilleure architecture choisie, le script Optimize teste dans leMakefile s’il existe une regle de visualisation de l’application. Si c’est le cas, il la compileet indique a l’utilisateur la commande pour lancer cette visualisation. Dans tous les cas,le script indique l’architecture la plus performante, et fourni un graphique genere d’apresles fichiers de donnees.

Page 53: Mémoire final du projet TER Programmation performante d ...

Chapitre 12

Description des extensions possibles

Voici une presentation des differentes extensions possibles du logiciel.

1. Implementation d’une interface graphiqueDescription : L’utilisateur pourrait, via une interface graphique, lancer le logiciel.Justification : Avec une interface graphique, le logiciel serait plus accueillant etressemblerait vraiment a un logiciel que l’on lance, plutot qu’a un simple script avec unterminal. L’utilisateur aurait la possibilite d’y indiquer les architectures proposees, lesarguments necessaires a la compilation du programme, etc.

2. Pas de duplication de codeDescription : Cela veut dire que toutes les implementations des differentes architec-tures proposees soient situees uniquement dans un seul fichier avec comme modelede programmation les templates, et qu’il n’y ait aucune duplication de code dans lesfonctions.Justification : Par rapport aux differents exemples de programmes qu’y ont etetraites, nous avons eu quelques soucis pour ne pas dupliquer completement le codepuisque deux architectures utilisees (CUDA et MPI) ont leur propre specification entemplate. Ceci est du a leurs nombreuses differences d’implementations comme parexemple les structures de donnees utilisees, les headers, etc. Il y a eu aussi des problemesa cause du preprocesseur car lors de la compilation d’une architecture donnee, lepreprocesseur allait tout de meme analyser le code des autres architectures, ce qui etaitun gros probleme si toutes celles-ci n’etaient pas supportees au depart par la machine.

3. Heterogeneite entre differentes architecturesDescription : A partir des differents exemples de programmes et d’architectures ba-siques proposes (ex : SSE, OPEMP, MPI), on pourrait melanger toutes ces architecturesentre elles, bien sur quand cela devient interessant et possible.Justification : C’est une facon d’aller plus loin dans l’heterogeneite de methodesde parallelisation. De plus, on obtiendrait un executable en sortie le plus performantpossible pour le programme en entree.

4. Detection automatique des architectures supportees par le programme en entreeDescription : Il pourrait etre plus commode de faire une analyse syntaxique ducode du programme passe en parametre pour y detecter l’utilisation d’architecturesparticulieres (inclusion de header specifique, utilisation de certaines directives ...).

53

Page 54: Mémoire final du projet TER Programmation performante d ...

Justification : Il n’y aurait plus besoin de maintenir la directive archi du Makefilelorsque l’on ajouterait plusieurs implementations.

5. Reutiliser les scripts de detection d’architecturesDescription : Lorsqu’on souhaite utiliser une application parallele heterogene (SSE+ OpenMP par exemple), il faut recreer un script de detection d’architectures mixtes(dispo.sh) alors qu’on possede deja un script de detection pour SSE d’une part, etOpenMP d’autre part.Justification : On evite le phenomene de duplication de code pour la partie dedetection.

6. Traiter d’autres exemples de parallelismeDescription : Il aurait pu etre interessant de tester des versions d’applicationspour d’autres types d’unites vectorielles (AltiVec, l’unite vectorielle des architecturesPowerPC). Concernant l’approche multi-cœurs, nous n’avons pas parle des processeursCELL (utilise par la PlayStation 3 de Sony). De meme, dans le cadre de la program-mation multi-cœurs, nous n’avons pas traite le cadre de la programmation pThread, ouencore Intel Threading Building Blocks (TBB)Justification : Plus on multiplie le nombre d’implementations paralleles, plus onpeut esperer obtenir de meilleurs performances.

7. Parallelisation automatique du programme en entreeDescription : Lorsque l’utilisateur ajoute un nouveau programme, il est oblige de leparalleliser lui-meme. Il serait donc interessant de faire en sorte que le logiciel puisse leparalleliser automatiquement.Justification : Cela permettrait a l’utilisateur de gagner beaucoup de temps. De pluscela pourrait etre tres utile pour les personnes n’ayant pas les connaissances necessairespour paralleliser un programme.

Page 55: Mémoire final du projet TER Programmation performante d ...

Chapitre 13

Planning

Pour realiser ce projet, nous avons effectue deux reunions hebdomadaires : une avec M.Couvreur, le charge de TD, et une seconde avec M. Jubertie, le client).

Description du planning.

La premiere semaine, nous avons eu un entretien avec M. Jubertie afin qu’il nous expliquece qu’il attendait de nous. Nous avons commence avec lui a definir les besoins du projet.Une fois avoir bien defini les besoins lors du second entretien, nous avons commence a nousdocumenter sur les outils que nous devions utiliser. Des que nous avions les connaissancesnecessaires pour maıtriser ces outils, nous avons commence la partie programmation.

Tout au long du projet, nous nous sommes documente tout en continuant a programmer.En effet, lors des entretiens avec M. Jubertie, il nous parlait d’outils que nous ne connaissionspas ce qui nous imposait de nous renseigner afin d’avoir plus d’informations a leur sujet.Une fois que nous avions fini de developper le probleme des N-Corps sous une architecture,nous commencions a faire des evaluations de performances sur celle-ci.

Dans la semaine du 1er Mars, nous nous sommes reparti les taches pour la redaction dumemoire intermediaire. A partir de cette date, tous les entretiens avec M. Jubertie et M.Couvreur etaient cibles sur le contenu du memoire afin d’etablir ce qu’il fallait et ce qu’il nefallait pas mettre dedans.

Une fois le memoire intermediaire redige et rendu, nous nous sommes documentes surla meta-programmation, de l’utilisation des templates. Ensuite, plusieurs exemples simples deprogrammes ont ete implementes pour une meilleur comprehension de l’architecture logicielleimposee pour l’utilisateur.

Pour finir, nous avons redige le memoire final.

Organisation au sein du groupe

Tout au long du projet, nous avons essaye de d’equilibrer le plus possible la repartition destaches.Nous avons commence par choisir un chef de projet. Romain s’est avere etre la personneideale pour ce poste car il a un bon sens de la communication et connait bien la capacite dechacun de nous afin de nous repartir le travail.

55

Page 56: Mémoire final du projet TER Programmation performante d ...

Clement a developpe les algorithmes sequentiels des N-Corps et du filtre de Sobel, ainsique toute la partie concernant la programmation CUDA. Il a aussi mis en place le serveurSVN afin que nous puissions travailler dessus.

Romain s’est occupe des implementations SSE (et OpenMP+SSE), effectue les evaluationsde performances et regarde le travail qu’a effectue le groupe afin d’y proposer des modifications.

Vincent a developpe les algorithmes paralleles en MPI et a effectue les evaluation deperformances pour sa partie. Il s’est egalement occupe de la realisation des schemas, ainsi quede la partie Bibtex.

Cedric a developpe la visualisation en OpenGL, l’implementation des problemes enOpenMP, a effectue des tests concernant l’utilisation de la genericite en C++ a l’aide detemplates, et s’est occupe de la partie GnuPlot.

Le script optimize.sh a ete developpe/ameliore conjointement par nous quatre.

Page 57: Mémoire final du projet TER Programmation performante d ...

Janvier 2010

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Définition des besoins

Documentation

Programmation

Tests de performance

Rédaction du mém. inter.

Février 2010

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

Définition des besoins

Documentation

Programmation

Tests de performance

Rédaction du mém. inter.

Mars 2010

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Méta-programmation

Documentation

Programmation

Tests de performance

Rédaction du mém. inter.

Rédaction du mém. final

Avril 2010

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

Méta-programmation

Programmation

Tests de performance

Rédaction du mém. final

Mai 2010

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Méta-programmation

Programmation

Tests de performance

Rédaction du mém. final

Fig. 13.1 – Planning

Page 58: Mémoire final du projet TER Programmation performante d ...
Page 59: Mémoire final du projet TER Programmation performante d ...

Chapitre 14

Annexes

Listing 14.1 – Code sequentiel

t e m p l a t e <u n s i g n e d i n t T> v o i d VectorAdd<T> : : add ( ) {i n t i ;f o r ( i =0; i < s i z e ; i ++) {

v3 [ i ] = v1 [ i ] + v2 [ i ] ;}

}

Listing 14.2 – SSE et SSE+OpenMP

t e m p l a t e <u n s i g n e d i n t T> v o i d VectorAdd<T> : : add ( ) {i n t i ;

m128 m1, m2 ;#i f ARCHI == SSEMP

#pragma omp p a r a l l e l p r i v a t e ( i , m1, m2)#pragma omp f o r

#e n d i ff o r ( i = 0 ; i < s i z e ; i=i +4) {

//On c h a r g e l e s v e c t e u r s dans l e s r e g i s t r e sm1 = mm load ps (&v1 [ i ] ) ;m2 = mm load ps (&v2 [ i ] ) ;//On e f f e c t u e l o p e r a t i o nm2 = mm add ps (m1, m2) ;//On t r a n s f e r t l e r e s u l t a t contenu dans l e r e g i s t r e v e r s l e

t a b l e a u en memoirem m s t o r e p s (&v3 [ i ] , m2) ;

}}

59

Page 60: Mémoire final du projet TER Programmation performante d ...

Listing 14.3 – Code OpenMP

i n t i ;#i f ARCHI == OPENMP

#pragma omp p a r a l l e l p r i v a t e ( i )#pragma omp f o r

#e n d i ff o r ( i =0; i < s i z e ; i ++) {

v3 [ i ] = v1 [ i ] + v2 [ i ] ;}

Listing 14.4 – Code CUDA

/∗∗∗ \ b r i e f K e r n e l n a i f d a d d i t i o n de v e c t e u r∗ \param v1 l e p r e m i e r v e c t e u r∗ \param v2 l e deuxieme v e c t e u r∗ \param v r e s l e v e c t e u r dans l e q u e l on s t o c k e r a l e r e s u l t a t∗ \param s i z e l a t a i l l e des v e c t e u r s∗/

g l o b a l v o i d VecAdd ( c o n s t f l o a t ∗ v1 , c o n s t f l o a t ∗ v2 , f l o a t ∗v r e s , i n t s i z e )

{// on r e c u p e r e l i n d i c e du t h r e a d c o u r a n t q u i e s t e g a l e a l

i n d i c e du b l o c c o u r a n t// ∗ l a t a i l l e d un b l o c k + l i n d i c e du t h r e a d dans l e b l o c k

c o u r a n ti n t i = blockDim . x ∗ b l o c k I d x . x + t h r e a d I d x . x ;// s i on e s t b i e n i n f e r i e u r a l a t a i l l e des v e c t e u r s a l o r s on

r e a l i s e l a j o u ti f ( i < s i z e )

v r e s [ i ] = v1 [ i ] + v2 [ i ] ;}

Listing 14.5 – Code OpenMPI

f l o a t ∗ VectorAdd<OPENMPI> : : getVAdd ( ) {MPI Gather ( v3 , t a i l l e T a b l e a u , MPI FLOAT , bu f r , t a i l l e T a b l e a u ,

MPI FLOAT , 0 , MPI COMM WORLD) ;r e t u r n b u f r ;

}

v o i d VectorAdd<OPENMPI> : : add ( ) {f o r ( i n t i = 0 ; i < t a i l l e T a b l e a u ; i ++)

v3 [ i ] = v1 [ i ] + v2 [ i ] ;}

Page 61: Mémoire final du projet TER Programmation performante d ...

Bibliographie

[1] Cuda case studies. Cuda Technical training, 2008. http://www.nvidia.com/docs/IO/47904/VolumeII.pdf.

[2] Introduction to cuda programming. Cuda Technical training, 2008. http://www.nvidia.com/docs/IO/47904/VolumeI.pdf.

[3] The message passing interface (mpi) standard, 2008. http://www.mcs.anl.gov/research/projects/mpi.

[4] G. Blelloch and G. Narlikar. A practical comparison of n-body algorithms. In ParallelAlgorithms, Series in Discrete Mathematics and Theoretical Computer Science. AmericanMathematical Society, 1997.

[5] P. Boulet. Introduction a OpenMP, may 2001. www2.lifl.fr/west/courses/cshp/openmp.pdf.

[6] CNRS-IDRIS. Livre OpenMP, september 2000. www.idris.fr/data/publications/openmp.pdf.

[7] Intel. A Hands-on Introduction to OpenMP, june 2006. http://www.univ-orleans.fr/sciences/info/ressources/Modules/master2/programmation-multi-coeurs/resolveUid/a080c45187805e7bcfad4cf89c6949c9.

[8] Intel. Intel R© C++ Intrinsic Reference, 2007. Document Number : 312482-003US.

[9] Intel. Intel R© 64 and IA-32 Architectures Software Developer’s Manual, 2009. Volume2A : Instruction Set Reference, A-M.

[10] M. Harris L. Nyland and J. Prins. Fast n-body simulation with cuda. GPU Gems 3, 2007.http://developer.nvidia.com/object/gpu-gems-3.html.

[11] P. Marquet. Introduction a MPI - Message Passing Interface, may 2001. www2.lifl.fr/west/courses/cshp/mpi.pdf.

[12] Nvidia. NVIDIA CUDA Compute Unified Device Architecture, Programming Guide, June2010. http://developer.download.nvidia.com/compute/cuda/3_0/toolkit/docs/NVIDIA_CUDA_ProgrammingGuide_3.0.pdf.

[13] L. Tricon. Message passing interface (mpi), november 2002. http://lionel.tricon.free.fr/Documentations/mpi/mpi.html.

[14] SIR universite de Cergy-Pontoise. Cours OpenMP, february 2006. www.u-cergy.fr/sir/data/coursOpenMP/coursOpenMP.ps.

61