• Aucun résultat trouvé

Dans le but de tester les modèles de performances développés au cours de cette thèse et le portage sur les processeurs Xeon et Xeon Phi d’Intel, nous allons détailler dans cette partie les trois applica-tions qui seront utilisées dans la suite de ce manuscrit. Il s’agit de la mini-application Advection 2D, développée dans le cadre de ce travail, qui servira de prototype pour caractériser et évaluer les codes hydrodynamiques explicites sur architectures Xeon et Xeon Phi, l’application HPL (High performance

LINPACK)1 qui permet de calculer les performances des machines et qui est largement utilisée dans

le domaine du calcul haute performance. Elle est notamment utilisée pour classer les centres de calcul

mondiaux au top5002 et enfin la micro-application STREAM3, qui permet de calculer et valider la

bande passante mémoire des machines.

3.1 Advection 2D

La mini-application advection 2D développée dans ce travail, utilise des méthodes numériques et des équations, qui sont très proches algorithmiquement de celles utilisées par le code de calcul FluxIC [17], développé au laboratoire CMLA de l’École Normale Supérieure de Cachan par l’équipe Simulation des fluides réels, et qui va être utilisée dans la deuxième partie de cette thèse pour la simulation d’im-pacts de vagues. La mini application Advection 2D servira de prototype pour l’évaluation des codes d’hydrodynamique explicites sur les architectures composées de processeurs Xeon et Xeon Phi, elle sera résolue par la méthode des volumes finis avec flux caractéristiques.

Cette application résout l’équation d’advection en deux dimensions, qui décrit la façon dont une quan-tité est transportée dans un courant.

3.1.1 Advection 2D : premier ordre en temps et en espace

∂u

∂t + div(~cu) = 0. (3.1)

L’équation d’advection (3.1) peut être écrite sous forme d’équation conservative, comme suit :

tu + div(F (u)) = 0, (3.2)

où u est le vecteur des variables conservatives (dans notre cas la densité) et F (v) le vecteur des flux, qui s’écrit dans notre cas F (u) = ~cu.

Cette équation admet une solution exacte connue, qui s’écrit par exemple en dimension 1 :

u(t, x) = u0(x− ct), c ∈ R, t ≥ 0, x ∈ [0, 1], (3.3)

avec une solution initiale :

u(x, 0) = u0(x). (3.4)

Il s’agit de la propagation sans diffusion à la vitesse constante c du profil initial u0(x).

Nous allons résoudre numériquement cette équation en 2D avec des conditions aux limites périodiques de période 1 sur [0, 1m] × [0, 1m].

Schémas numérique : VFFC Volumes finis

On veut maintenant discrétiser les équations par la méthode des volumes finis.

En intégrant (3.2) sur un domaine Ω de bord Γ, on obtient :

Z (∂tu + div(F (u))) dτ = d dt Z udτ + Z Γ F.ν ds, (3.5) 1. http ://www.netlib.org/benchmark/hpl/ 2. https ://www.top500.org/ 3. http ://www.cs.virginia.edu/stream/

avec ν, le vecteur unitaire normal à Γ et orienté vers l’extérieur.

Le domaine est discrétisé en espace par N mailles Ωi et en temps (pas de temps ∆t). Sur la maille

i∈ [[1, N]], au temps tn= n∆t, l’équation devient : |Ωi|U n+1 i − Uin dt + Fi X f =1 |Aff = 0, (3.6) où Uin= 1 |Ωi| Z Ωi Undτ, (3.7)

et |Ωi| est l’aire de la maille i, Fi est le nombre de bords de la maille i et |Af| est la longueur de la face f ∈ [[1, Fi]]. Avec φf, le flux numérique à travers la face f de la maille i dans la direction de la normale extérieure à Ωi, nf, il est donné par :

φf = 1 |Af| Z Γf F · nfds. (3.8) L’équation (3.5) devient : un+1i,j − un i,j ∆tn + 1 ∆x∆y[(φ n

i+1/2,j− φni−1/2,j)∆y + (φni,j+1/2− φni,j−1/2)∆x] = 0. (3.9) La méthode des volumes finis nous permet donc d’exprimer les grandeurs conservatives au temps tn+1 en fonction des grandeurs au temps tn et du flux φf.

En utilisant la méthode des volumes finis à Flux Caractéristiques VFFC développée dans [43], le flux numérique est calculé de la façon suivante :

φ(Γ, nf) = 

F (Ul) + F (Ur)

2 − signJ(UΓ, nlr)F(Ur)− F (Ul) 2



· nlr, (3.10)

où J(UΓ, nlr) est définie par :

J(UΓ, nlr) = ∂F (UΓ).nlr

∂U , (3.11)

et UΓ est égal au vecteur U sur la surface commune aux deux cellules, Cl la cellule de gauche (à

laquelle est associée Ul) et la cellule de droite Cr (à laquelle est associée Ur) et qui se calcule comme suit :

UΓ= V olrUr+ V ollUl

V olr+ V oll , (3.12)

avec V olr et V oll les aires des cellules Cr et Clrespectivement, nlr le vecteur normal à Γ, dirigé de Cl vers Cr et signJ(UΓ, nlr)

est la matrice signe donnée par : sign

J(UΓ, nlr)

= R(UΓ.nlr) diag(sign(λ(UΓ.nlr)) L(UΓ.n), (3.13)

avec λ(UΓ.nlr) les valeurs propre de J(UΓ, nlr) associés aux matrices des vecteurs propres à droite et à gauche respectivement, R(UΓ.nlr) et L(UΓ.nlr).

Pour un flux physique F donné, dans notre cas (advection) F = ~c.u, le flux numérique est défini par :

φni+1/2,j = F (un i,j) + F (uni+1,j) 2 − sign(~c.nlr)F (u n i+1,j)− F (un i,j) 2  .nlr (3.14)

Splitting directionnel

Pour calculer les grandeurs un+1

i,j en fonction des valeurs du pas de temps précédent, on utilise une

méthode qui découple l’équation (3.9) en deux directions. Nous allons introduire un temps intermédiaire

t pour avoir les équations suivantes :          un i,j− un i,j ∆tn + 1 ∆x h φni+1/2,j+ φni−1/2,ji = 0, un+1i,j − un∗ i,j ∆tn + 1 ∆y h φni,j+1/2+ φni,j−1/2i = 0. (3.15) Cette décomposition, nous permet de transformer notre problème 2D en deux équations 1D. Ce qui va faciliter la parallélisation et l’accès aux tableaux dans la version parallèle du code advection 2D décrite plus loin.

Stabilité du schéma Le schéma numérique présenté dans le paragraphe précédent est explicite,

d’ordre 1 en temps et en espace, pour assurer la stabilité du schéma, le pas temps est calculé en respectant la condition de stabilité CFL (Courant-Friedrichs-Lewy) suivante :

dt≤ min  V oli |Af| maxk|(λi)k|  . (3.16)

Dans notre cas |(λi)k| est la vitesse c(i)k dans la cellule k et selon la direction i.

3.1.2 Implémentation

Nous avons implémenté un solveur 2D d’advection avec la méthode VFFC décrite ci-dessus en Fortran 90 avec plusieurs niveaux de parallélisme.

Parallélisation MPI

Nous utilisons la bibliothèque MPI (Message Passing Interface) pour la décomposition du domaine de calcul, qui représentera dans le cadre de cette étude, un maillage cartésien avec un pas d’espace constant.

Le schéma développé dans le paragraphe précédent, est un schéma dit à 5 points, c’est-à-dire pour

connaître la valeur de un+1

i,j à l’instant tn, nous avons avons besoin des 4 autres points voisins du

maillage à l’instant tnaux positions suivantes :

Figure 3.1 – Schémas à 5 points

Le modèle que nous avons choisi pour cette parallélisation est un modèle SIMD (Single Instruc-tion Multiple Data). Un seul programme s’exécute sur chaque sous domaine, et donc il y a autant de processus MPI que de sous domaine. Pour chaque sous domaine, nous avons besoin de connaître les positions de ses 4 voisins pour pouvoir échanger les informations entres les voisins pendant chaque pas de temps.

À l’aide des fonctionnalités MPI de création de grilles, nous avons défini une topologie virtuelle de type cartésienne, qui fait correspondre les sous domaines de calcul à une grille de processus et permet de

répartir les processus de manière régulière sur le domaine de calcul. En effet La décomposition en grille 2D de processus à l’avantage d’avoir un attribut supplémentaire pour mieux coller à la réalité (grille de processeurs dans clusters de travail) contrairement à une numérotation classique des processus de 1 à (np -1).

Cette topologie attribue à chaque rang MPI des coordonnées (i,j), tel que i ∈ [0, Nx] et j ∈ [0, Ny],

avec Nx et Ny, respectivement le nombre de sous domaines (et donc de processus) suivant x et y.

Pour chaque sous-domaine de calcul, nous allons créer des cellules fantômes qui contiendront les infor-mations des cellules des sous domaines voisins, que nous allons ensuite communiquer à chaque pas de temps (elle seront nécessaires pour le calcul des flux sur les cellules de bords). La Figure3.2représente un exemple de décomposition de domaine d’une grille cartésienne 9 × 9, en 9 sous domaines 3 × 3 selon la stratégie de décomposition décrite ci-dessus.

Figure 3.2 – Exemple d’une décomposition d’un domaine de calcul en 9 sous domaines MPI

Nous avons choisi d’effectuer les communications des flux physiques entre sous-domaines avec des communications MPI asynchrones non bloquantes (MPI_Isend, MPI_Irecv). Ainsi les fonctions paral-lèles pourront "rendre la main" avant que la communication ne soit terminée. On peut donc placer

MPI_Isendet MPI_Irecv sans avoir de blocage (c’est-à-dire qu’un processus n’attend pas indéfiniment

un message qui ne viendra pas).

Nous rajoutons en aval des fonctions MPI\_Wait pour attendre la fin des envois et des réceptions avant de poursuivre les calculs. Ces fonctions vont nous permettre d’effectuer des communications en même temps entre les processus.

MPI offre l’avantage de pouvoir optimiser ces communications grâce à des types que l’on va pouvoir définir pour éviter de faire des communications donnée par donnée, nous allons par exemple définir un type que l’on va appeler "type_vect_ew" (type_vect_ns) qui va contenir toute une colonne (resp ligne) à envoyer (resp recevoir) en une seule fois au processus (sous domaine) voisin.

Parallélisation OpenMP

Les calculateurs et même les stations de travail actuels contiennent plusieurs niveaux hiérarchiques de parallélisme, contenant des nœuds composées de processeurs multi-cœurs avec plusieurs niveaux de mémoire.

Pour améliorer les performances de la version MPI du code, un niveau de parallélisme à mémoire partagée s’appuyant sur l’utilisation du langage OpenMP a été introduit. Les sous domaines sont définis par la grille de calcul MPI, et dans chaque sous domaine on utilise des pragmas OpenMP pour

tirer profit de la mémoire partagée. Compteurs dans le code

Pour connaître les durées passées dans chaque partie du code à chaque exécution, nous avons introduit dans le code des compteurs de temps pour différentes parties d’intérêt. Les temps sont calculés à l’aide des fonctions MPI, .MPI_Wtime pour récupérer les temps passés sur chaque partie par chaque processus MPI. À la fin de la simulation nous réalisons une réduction avec la fonction MPI_ALLREDUCE, qui permettra le calcul du temps passé sur chaque partie du code pour l’ensemble des processeurs.

Les temps les plus importants à afficher à la fin de chaque simulation sont ceux associés : — au calcul ;

— aux communications MPI entre processus ; — aux entrées/sorties dans le code.

Ainsi nous pourrons avoir des courbes de scalabilité indépendantes pour détecter plus facilement si l’un de ces trois composants majeurs subit un problème de scalabilité.

Les entrées/sorties dans le code Advection 2D Écriture en ASCII

Nous avons choisi d’implémenter les sorties du code advection de la même manière que dans le code de calcul FluxIC.

Les sorties du code advection 2D, sont écrites dans un fichier en ASCII en double précision, avec un format d’écriture de type VTK, qui contiendra les informations sur le maillage (type de maillage, pas d’espace, dimension du maillage) dans l’en-tête du fichier de sortie, et toutes les données que nous

souhaitons écrire (pour la validation et la visualisation) durant la simulation. La Figure3.3représente

un exemple de sortie sous format VTK.

Figure 3.3 – Exemple de sortie de type VTK

Dans la version ASCII d’écriture des sorties, chaque processus écrit dans un fichier de type VTK les données de la simulation dans le sous domaine qui lui a été attribué en ASCII, et il sera nommé en

fonction du rang du processus MPI (voir Figure 3.4).

Nous avons développé un programme de post traitement, qui se chargera à la fin de la simulation de récupérer tous les fichiers de sorties produits par les processus pour chaque pas temps, pour ensuite les assembler dans un seul fichier par pas temps d’affichage et pour tout le domaine de calcul.

Figure 3.4 – Procédure des écritures parallèle des résultats en ASCII

Les fichiers de sorties finaux seront ensuite lus par des logiciels open-source pour la visualisation

2D-3D, comme Paraview4 ou Visit5

Au cours de nos tests de performances, nous avons constaté que les écritures de sorties en ASCII présentaient 60 % du temps total de la simulation.

Nous avons donc décidé d’implémenter plusieurs méthodes d’écriture des sorties et de refaire des tests de performance pour élire la version la moins consommatrice en temps et en espace mémoire.

Les différentes versions des écritures des sorties implémentées sont : — écriture en ASCII en simple et double précision ;

— écriture en binaire simple et double précision ; — écriture des sorties à l’aide de MPI I/O ; — écriture des sorties par nœud de calcul. I/O binaire

Nous avons suivi la même procédure des écritures dans le cas des I/O en ASCII, la seule différence réside dans l’écriture des résultats. Dans cette implémentation nous écrivons deux fichiers de sortie au lieu d’un seul pour la version ASCII :

— Fichiers des en-têtes : ce fichier contiendra l’en-tête du fichier VTK, à savoir le maillage, le pas d’espace, l’instant de sortie,... ;

— Un fichier qui contiendra les résultats de la simulation, mais cette fois-ci écrits en binaire. À la fin de la simulation nous obtiendrons pour chaque processus MPI, deux fichiers de sorties, qui seront ensuite fusionnés en un seul fichier, à l’aide d’un outil de post-traitement, avant d’être fusionnée par la suite en un fichier par pas de temps d’affichage pour tout le domaine.

Cette méthode présente l’avantage d’écrire les données en binaire au lieu d’ASCII, ce qui va nous permettre d’optimiser le temps d’écriture des résultats et le volume des sorties stockées.

La Figure3.5illustre la procédure suivie pour les écritures des résultats en binaire.

4. www.paraview.org

Figure 3.5 – Procédure des écritures parallèles des résultats en binaire

MPI I/O

Cette implémentation utilise les fonctionnalités MPI qui permettent l’écriture des données de tous les processus à un pas d’affichage donné dans un seul fichier.

Elle permettra la création d’un fichier de sortie et ensuite s’occupera de placer les données de chaque fichier dans un emplacement approprié à partir des rangs des processus.

La Figure3.6illustre la procédure suivie pour les écritures des résultats avec MPI I/O.

Figure 3.6 – Procédure des écritures parallèles des résultats par MPI I/O.

I/O par nœud

L’écriture des résultats avec les méthodes décrites précédemment, dans un contexte où l’on exécute notre programme sur un super-calculateur avec plusieurs nœuds de calcul, peut créer des ralentisse-ments du code, dus aux augmentations des temps de latence des accès à la mémoire, car si tous les

processus écrivent au même temps le système devient chargé.

Pour éviter ces problèmes de latence, nous avons décidé de développer une nouvelle méthodologie où seul le processus maître dans chaque nœud de calcul, écrira les informations collectées par les processus appartenant au même nœud.

Pour cela nous allons créer un communicateur MPI, qui lui s’occupera juste des processeurs de chaque nœud de calcul (et pas tous processeurs, comme le communicateur global), ci-dessous la pro-cédure suivie :

— MPI_Get_processor_name (my_node,&namelen) : cette fonction va nous permettre d’extraire le nom de chaque nœud de calcul ;

— à partir des noms des nœuds, nous attribuons un numéro unique pour chaque nœud (à l’aide d’une fonction de hachage) ;

— MPI_Comm_split (MPI\_Comm_world,couleur,clef,Commonde,code) : nous créons ensuite un sous communicateur "commonde" pour chaque nœud de calcul à partir de sa couleur ;

— MPI_Comm_rank( Commnode,iproc_new,code) : on récupère les nouveaux rangs des processus dans le nouveau communicateur. On aura ainsi les rangs des processus maîtres de chaque nœud de calcul ;

— Dans la fonction des écritures des résultats le processus maître de chaque nœud récupérera les données à écrire des processus de son nœud et écrira les données de sortie relatives à son nœud sous forme binaire.

La Figure3.7 illustre la procédure suivie pour l’écriture des résultats sous forme binaire.

Figure 3.7 – Procédure des écritures parallèle des résultats par nœud

Comparaison des méthodes des écritures des sorties

Les premières comparaisons entre différentes méthodes des écritures des sorties développées dans

les paragraphes précédents, ont été réalisés sur la machine Curie6, à l’aide de 10 nœuds de calcul avec

16 processeurs pour chaque nœud.

Ces résultats montrent que la méthode avec les écritures par nœud, donne des meilleurs résultats en termes de temps d’écriture jusqu’à une taille donnée du maillage et devient moins efficace que la version avec les écritures binaires par chaque processeur.

La perte d’efficacité de la méthode d’écriture par nœud est due à l’augmentation de la taille des données envoyées par chaque processus au processus maître dans un nœud donné.

La version MPI I/O donne des résultats très mauvais, à cause de l’écriture de tous les processus dans un seul fichier, ce qui est équivalent à une écriture séquentielle des sorties.

En conclusion la méthode des écritures des résultats en binaire pour chaque processus est élue la meilleure procédure dans notre cas d’étude. Les temps d’écriture par la version binaire par processus, en double et en simple sont très proches, mais la taille des fichiers stockés en binaire en simple précision est 60% moins importante que les fichiers stockés par la version en double précision. Donc nous avons choisi la méthode avec des sorties binaire en simple précision pour les écritures des résultats de notre mini-application advection 2D.

Premiers résultats de simulation

Les premières simulations ont été réalisées sur le cas test d’une poche carrée, se déplaçant à une vitesse constantes ~c = (4 m/s, 4 m/s), avec des conditions aux limites periodiques, sur un maillage

cartésien 120 x 120 (voir Figure3.1). Ces premiers résultats nous permettent de constater que la poche

de densité élevé se déplace dans la direction de la vitesse ~c et se déforme au cours du temps, à cause de la diffusion numérique, due à l’imprécision du schéma d’ordre 1.

Table 3.1 – Advection 2D, ordre 1 avec ~c = (4 m/s, 4 m/s), N=120 x 120, et des conditions limites

périodiques avec ; en haut à gauche t = 0s, en haut à droite t = 0.1s, en bas à gauche t = 0.1875s et en bas à droite t = 0.3125.

Nous avons réalisé une première étude de performances (très quantitative, nous allons effectuer une analyse plus détaillée des performances dans les chapitres suivants) du code Advection2D, avec le cas tests suivants :

— Maillage : 1600 x 1600 ; — CFL : 0.5 ;

— Nombre de pas de temps : 3200.

Nous avons calculé à la main le nombre des opérations flottantes dans le code (nombre des multi-plications/ additions) qui correspond à 475 Gflops et le nombre des accès mémoire qui correspond à 1048 GByte.

Le temps d’exécution de ce cas test est de 43.5 s et donc la performance de notre application en terme d’opérations flottantes est de 10.99 Gflops/s.

Avec les grandeurs calculées précédemment, nous définissons une nouvelle variable appelée intensité arithmétique de l’application AI = Nombre d’opérations flottantes

Nombre d’Accès mémoire = 0.45Flops/Bytes.

Les caractéristiques de la machine étant données par le constructeur, elles correspondent pour une machine utilisée dans notre cas composée de processeurs Sandy Bridge d’Intel à :

— la puissance théorique de la machine (Peak_machine) : 356 Gflops/s en double précision ; — la bande passante (BW) : 80 Gbytes/s.

Donc la performance maximale que l’on peut atteindre avec cette machine et notre application est : Peak (machine, application) = min(BW x AI = 36 Gflops/s, Peak_machine= 356 Gflops/s ) = 36 Gflops/s.

Cette étude préliminaire, nous montre que nous sommes à 27 % de la performance maximale que l’on peut atteindre avec cette application et sur cette machine et à 3% de la puissance crête de la machine. Pour tirer profit des performances de la machine, nous avons besoin, d’optimiser l’application pour nous approcher de la performance maximale que l’on peut atteindre avec ce couple (Machine, application) et augmenter le nombre d’opérations dans notre application pour profiter pleinement des ressources non utilisées de la machine. Ce qui va nous permettre d’utiliser au mieux les processeurs de notre plateforme.

Nous allons donc traiter plus d’opérations flottantes, mais avec un temps de calcul qui est proche de la version initiale. En d’autres termes, nous déplaçons beaucoup de données sans faire beaucoup de calcul.

Pour augmenter les opérations flottantes dans le code, nous avons décidé de réaliser une montée en ordre (ordre 2 en temps et en espace), pour obtenir des résultats plus précis en un temps théoriquement identique, et ensuite l’optimiser pour se rapprocher de la puissance crête de la machine.

La montée en ordre va nous permettre aussi de limiter la diffusion numérique observée au premier ordre.

3.1.3 advection 2D : second ordre en temps et en espace

Ordre 2 en espace, méthode MUSCL

L’ordre 2 en espace est obtenu en utilisant le schéma MUSCL (Monotone Upstream-Centred Scheme for Conservation Laws) écrit originellement par Van Leer [98]. Dans la formulation volumes finis à

l’ordre 1, ui(x) est choisi constante par maille. Dans le schéma MUSCL, cette grandeur est choisie

linéaire par morceaux avec :

∀x ∈ [xj−1/2, xj+1/2], u(x) = uj+ ∆xjθjSj, (3.17)

avec xj−1/2et xj+1/2 sont les positions des interfaces qui limitent la maille j, Sj la pente de uj dans la maille j et θj est un limiteur de pente utilisé pour limiter la pente si celle-ci tend vers l’infini, nous avons choisi pour cette étude le limiteur minmod [80] défini comme suit :

Nous posons ri le ratio des gradients successifs :

Documents relatifs