6. Quelques conseils pour optimiser les performances#
On formule ici quelques conseilspour aider l’utilisateur à tirer parti des diagnostics tracés dans le fichier message. Mais il faut bien être conscient qu’il n’y a pas de recette universelle pour optimiser les performances globales d’un calcul. Cela dépend du type d’étude, des aspects logiciels et matériels de la machine, voire de sa charge!
Le paramétrage par défaut et les affichages/alarmes du code proposent un fonctionnement équilibré et instrumenté . Mais, pour être sûr d’avoir utilisé au mieux les capacités de sa machine, l’utilisateur doit rester attentif aux éléments décrits dans ce document ainsi qu’aux conseils qui fourmillent dans les documentations des commandes.
On liste ci-dessous et, de manière non exhaustive, plusieurs questions qu’il est intéressant de se poser lorsqu’on cherche à optimiser les performances de son calcul. Bien sûr, certaines questions (et réponses) sont cumulatives et peuvent donc s’appliquer simultanément.
6.1. Concernant les caractéristiques du problème#
Au vu des éléments du §3, on peut formuler deux règles empiriques:
Plus la taille du problème (\(N\) ) et/ou le remplissage de la matrice (\(\mathit{NNZ}\) ) augmentent, plus la construction, et surtout, la résolution du système linéaire vont être coûteux (CPU/RAM).
L’augmentation de la proportions de Lagrange (\({N}_{L}/N\) ) peut rendre plus difficile la résolution du système linéaire (temps d’exécution, qualité de la solution).
La taille du problème dimensionne le nombre maximum de processeurs qu’il est pertinent de consacrer à son calcul parallèle: une granularité d’au moins 20.103 degrés de liberté par process MPI est requise.
6.2. Concernant les temps consommés#
Pour diminuer les temps CPU, l’utilisateur Aster dispose de différents outils:
Si l’essentiel des coûts concerne les calculs élémentaires/assemblages et/ou les résolutions de systèmes linéaires (cf. §4.1) on conseille d’utiliser Aster en mode parallèle. Il est alors préférable d’utiliser le solveur linéaire MUMPS en mode MPI distribuée ou le solveur MULT_FRONT en OpenMP. La première stratégie permet aussi de faire baisser les consommations RAM par processeur.
Si on utilise déjà le solveur linéaire MUMPS , on peut désactiver ses fonctionnalités d’OOC [12]_
(GESTION_MEMOIRE=”IN_CORE”) et d’amélioration de la qualité de la solution (RESI_RELA=-1.d0). Si la matrice est bien conditionnée et/ou non symétrique, on peut aussi essayer des paramètres de relaxation du solveur linéaire (FILTRAGE_MATRICE, MIXER_PRECISION, SYME).
Si on effectue un calcul non linéaire on peut tester différents paramètres de relaxation du solveur non linéaire (REAC_INCR, REAC_ITER, SYME).
Si on effectue un calcul modal , on conseille l’utilisation de la méthode IRAM (METHODE=”SORENSEN”) et le découpage du spectre recherché en plusieurs bandes fréquentielles ( via l’opérateur CALC_MODES avec OPTION=”BANDE” découpée en plusieurs sous-bandes).
De manière générale, plus le mode de fonctionnement de JEVEUX (et MUMPS) est en IC (cf. §5.1), plus le calcul est rapide. Ces gains ne sont cependant pas très importants comparés à ceux que procurent le parallélisme et le choix d’un solveur linéaire (avec le paramétrage) adapté.
Pour chaque étape du calcul, on doit normalement avoir un temps système faible (SYST) et un cumul «temps CPU+temps système» (CPU+SYST) très proche du temps d’attente réel (ELAPS). Si ce n’est pas le cas, deux cas de figures classiques peuvent se présenter:
Le temps USER+SYSest très supérieur au temps ELAPS . Le calcul utilise certainement le parallélisme OpenMP des stratégies parallèles 1c ou 2a décrites dans la doc. U2 sur le parallélisme et au §2.2 de la doc. U4.50.01. Cette situation n’est pas inquiétante (et même voulue), le principale étant que le temps de retour du calcul soit le plus bas possible !
Le temps ELAPSest très supérieur au temps CPUet/ou le temps SYSest important. Le calcul pâtit probablement de contentions mémoires (swap système, I/O RAM/disque…).
Piste n°1: Ce surcoût peut provenir des déchargements globaux de JEVEUX . Pour s’en convaincre il suffit d’aller lire, en fin de .mess, les statistiques concernant l’allocation dynamique (cf. §5.2) ou les consommations en temps des différents déchargements (cf. §4.1). Plus il y a eu d’appels aux mécanismes de libération et/ou de gros objets libérés, plus les temps SYST et ELAPS vont se dégrader. Une solution est alors d’augmenter la taille mémoire dévolue à JEVEUX .
Piste n°2: Corollaire du constat précédent, le mode parallèle MPI a tendance a décupler les surcoûts dûs aux déchargements. En effet, la distribution des données induites par le parallélisme va diminuer la taille des objets JEVEUX (si SOLVEUR/MATR_DISTRIBUEE est activée) et donc limiter l’impact des déchargements. A contrario , ceux-ci risquent de s’effectuer en même temps et sur des processeurs contiguës. Une solution palliative peut alors consister à «gâcher» du processeur, en entrelaçant les processeurs MPI actifs de processeurs dormants (par ex. valeur ncpus d” Astk initialisée à 2).
Piste n°3: Si on utilise le solveur linéaire MUMPS en OOC, le problème peut provenir d’un grand nombres de descente-remontées du produit (cf. étape 1.4 de §4.1). On peut les limiter en débranchant l’option de raffinement automatique (RESI_RELA=-1.d0) ou en repassant en mode IC (si les ressources mémoires le permettent).
6.3. Concernant la mémoire RAM consommée#
Si les consommations \({\mathit{JE}}_{\mathit{IC}}\) et \({\mathit{JE}}_{\mathit{OOC}}\) sont proches (quelques dizaines de pourcents), c’est que JEVEUX a dû souvent fonctionner en mode OOC. Il y a eu probablement de nombreux déchargements mémoire (cf. §4.1/5.2). Le temps de calcul peut en pâtir (surtout en parallèle). Il faut essayer de rajouter de la mémoire ou augmenter le nombre de processeur (si l’option MATR_DISTRIBUEE est activée).
Le calcul est souvent dimensionné en mémoire , par le maximum, sur l’opérateur le plus gourmand, entre la valeur plancher de JEVEUX (\({\mathit{JE}}_{\mathit{OOC}}\) ) et la valeur utilisée par le solveur linéaire MUMPS (si on l’utilise). Pour diminuer ce chiffre on peut jouer sur plusieurs facteurs:
Si on utilise MUMPS et que ce dernier est prédominant (c’est souvent le cas): utiliser plus de processeurs en MPI (paramètres mpi_nbcpu/mpi_nbnoeud d” Astk ), passer en OOC (mot-clé SOLVEUR/GESTION_MEMOIRE). Si la matrice est bien conditionnée et/ou non symétrique, on peut aussi essayer des paramètres de relaxation du solveur linéaire (FILTRAGE_MATRICE, MIXER_PRECISION, SYME).
Si on utilise MUMPS et que ce dernier n’est pas prédominant: utiliser la distribution d’objets JEVEUX de l’option MATR_DISTRIBUEE en mode parallèle.
Si on utilise un autre solveur linéaire : utiliser MUMPS en parallèle voire GCPC/PETSC (en séquentiel/parallèle).
6.4. Concernant le parallélisme#
Si l’essentiel des coûts concerne les calculs élémentaires/assemblages et/ou les résolutions de systèmes linéaires (cf. §4.1) on conseille d’utiliser Aster en mode parallèle. Il est alors préférable d’utiliser le solveur linéaire MUMPS en mode MPI distribuée (cf. doc. U2 sur le parallélisme ou U4.50.01) ou le solveur MULT_FRONT en OpenMP.
Il est préférable de limiter le parallélisme du calcul uniquement aux quelques opérateurs coûteux (en temps/mémoire): STAT/DYNA_NON_LINE, CALC_MODES … Et donc, si possible, de découper celui-ci en une succession de pré/post-traitements séquentiels et de calculs parallèles. Lors de longs calculs (quelques jours), cette stratégie permet en outre de mieux se prémunir contre d’éventuels arrêts en erreur en sauvegardant la base associée à chaque grande étape du calcul.
Il est intéressant de valider , au préalable, son calcul parallèle en comparant quelques itérations en mode séquentiel et en mode parallèle. En outre, cette démarche permet aussi de calibrer les gains maximums atteignables (speed-up théoriques) et donc d’éviter de trop «gaspiller de processeurs». Ainsi, si on note \(f\) la portion parallèle du code (déterminée par exemple via run séquentiel préalable), alors le speed-up théorique \({\mathrm{S}}_{\mathrm{p}}\) maximal accessible sur \(p\) processeurs se calcule suivant la formule d’Amdhal (cf. [R6.01.03] §2.4):
\({\mathrm{S}}_{\mathrm{p}}=\frac{1}{1-\mathrm{f}+\mathrm{f}/\mathrm{p}}\) .
Par exemple, si on utilise le parallélisme distribué par défaut (scénarios 1b+2b, cf. doc. U2 sur le parallélisme) et que les étapes 1.3/1.4 et 2. (cf. §4.1) représentent 90% du temps séquentiel (\(f=0.90\) ), le speed-up théorique est borné à la valeur \({\mathrm{S}}_{\infty}=\frac{1}{1-0.9+0.9/\infty }=10\) ! Et ce, quelque soit le nombre de process MPI alloués.
Pour optimiser son calcul parallèle, il faut surveiller les éventuels déséquilibres de charges du flot de données (CPU et mémoire) et limiter les surcoûts dûs aux déchargements mémoire (JEVEUX et MUMPS OOC) (cf. §4.1)et aux archivages de champs (cf. §4.3). Pour gagner en temps calcul, il faut aussi proscrire toute procédure de retassage de la mémoire (commande FIN mot-clé RETASSAGE) qui est contre-productive en parallèle.
Le recours au parallélisme MPI de MUMPS permet de gagner en temps CPU (sur les étapes parallélisées) et en mémoire RAM: grâce à la distribution de données de JEVEUX (si l’option MATR_DISTRIBUEE est activée) et, surtout, grâce à celle des objets MUMPS.
Quelques chiffres empiriques: on conseille d’allouer au moins 20 000 degrés de liberté par processus MPI ; Un calcul thermo-mécanique standard bénéficie généralement, sur 32 processeurs, d’un gain de l’ordre de la dizaine en temps elapsed et d’un facteur 4 en mémoire RAM.