Evolution des processeurs : quoi anticiper pour les codes ?
By , and   |  May 12, 2014

III – Recommandations et bonnes pratiques

Cette section recense quelques recommandations et bonnes pratiques à considérer pour migrer un code legacy ou pour concevoir un nouveau code de simulation. Il est fondamental d’inscrire ces recommandations et bonnes pratiques dans un contexte de développement afin qu’elles soient en adéquation avec les moyens, les utilisateurs et les technologies disponibles. Le dernier forum ORAP a d’ailleurs révélé l’existence d’une importante variété de processus de développement – certains plus orientés vers une large communauté scientifique internationale, d’autres se destinant plutôt aux petites équipes intégrées.

Validation – La validation doit être un processus continu au sens de l’ingénierie logicielle comme, par exemple, celle mise en œuvre dans Hudson [26]. La reproductibilité des résultats doit être assurée sans toutefois imposer de comparaisons bit à bit qui s’obtiennent au détriment de l’efficacité parallèle. La cohérence et la vérification visuelle des données sont essentielles pour avoir confiance dans les résultats obtenus. Cet aspect doit faire partie intégrante de la migration ou du développement d’un code.

Localité des données – Localité, affinité et volume des données sont des considérations critiques. Les structures internes des codes doivent en effet pouvoir évoluer facilement afin de s’adapter aux architectures cibles. Il faut par exemple prévoir des structures de tableaux pouvant être converties en tableaux de structures, et vice versa, en fonction de la configuration du nœud de calcul. La sérialisation des structures de données doit aussi être privilégiée puisqu’elles peuvent, à un moment ou un autre, migrer sur des unités de stockage différentes, comme la mémoire des accélérateurs ou des mémoires non-volatiles, par exemple. Ces considérations tendent à favoriser les implémentations C++, avec notamment l’usage de templates, mais leur maintenance relativement complexe fait qu’elles restent controversées.

Scalabilité – Les opérations collectives telles que les réductions passent difficilement à l’échelle. Elles doivent donc être évitées tout en privilégiant les échanges de données entre voisins. A ce titre, l’usage de solveurs minimisant les communications est également à privilégier. Bien entendu, ces options restent dépendantes de décisions prises en amont lors de la conception des structures de données et des schémas numériques.

Langages de programmation – Pour le choix d’un langage de programmation, ce sont les compétences des développeurs qio doivent être considérées en premier lieu. Les seuls langages universellement supportés sur les machines HPC actuelles sont C, C++ et Fortran qui, contrairement à beaucoup de critiques, reste un choix pertinent. D’autres nouveaux langages de programmation tels que PGAS sont certes prometteurs mais manquent d’une certaine masse critique et de visibilité. Les aspects portabilité, pérennité, efficacité, disponibilité de bibliothèques sont autant d’aspects qui peuvent guider le choix d’un langage.

Dans certains cas, les meilleures mises en œuvre combinent C++, pour l’aspect “framework“, et Fortran pour les parties calculatoires. C’est le cas, par exemple, d’Arcane [27].

L’utilisation combinée de langages demande toutefois à être murement réfléchie car, pour assurer leur bonne interopérabilité, des interfaces stables dans le temps doivent être développées en interne. S’appuyer sur des équipes aux multiples compétences conduit naturellement à des codes multi-langages mais, au risque d’aboutir à de gros problèmes de compilation, cette pratique devrait être restreinte.

Approches spécifiques à un domaine – Parce qu’elles fournissent aux scientifiques un niveau élevé d’abstraction, les approches DSL (domain specific language) [28] sont de plus en plus étudiées. Elles offrent en effet des mises en œuvre relativement sophistiquées basées sur des runtime et des bibliothèques spécifiquement optimisés. Ces approches peuvent faire le pont entre différentes communautés mais, attention, elles peuvent aussi entraîner des ruptures technologiques.

Dans le même ordre d’idée, les approches DSL embarquées offrent une alternative intéressante. Elles consistent en effet à inclure les DSL dans un langage générique comme Fortran. Les parties DSL apportent alors des informations de sémantique additionnelles, voire des stratégies de génération de code, tout en conservant le code de référence qui reste écrit dans un langage standard. Les langages basés sur les directives [29, 30] ainsi que d’autres mises en œuvre qui font appel aux templates C++ [31, 32] entrent aussi dans cette catégorie. Mais ces approches peuvent être limitées du fait du modèle de données sur lequel elles reposent, qui reste inhérent au langage générique.

Processus de développement et infrastructure – Plus les erreurs sont détectées tardivement, plus leur coût augmente. Parallèlement, de mauvaises pratiques de développement tendent à déstructurer un code qui sera ensuite plus difficile à maintenir. Rien de nouveau à ce sujet mais, dans le cadre d’un projet de parallélisation massif, l’environnement de développement devient si complexe qu’il nécessite une méthodologie à la fois robuste et bien organisée.

Le processus de développement doit donc intégrer des méthodes et techniques de l’ingénierie logicielle telles que :

– le contrôle par des outils automatiques (intégration continue) ;

– la gestion des versions de code ;

– des mesures de performance intégrées au code ;

– des tests unitaires ;

– des bases de non régression ;

– etc.

Tolérance aux pannes – Le niveau de tolérance aux pannes matérielles requis par les systèmes exaflopiques n’est pas encore réellement disponible mais le coût des entrées/sorties oblige à mettre en œuvre dès à présent des stratégies qui minimisent les volumes de données nécessaires au checkpoint-restart [33, 34, 35]. A ce titre, les approches applicatives plutôt que systèmes sont actuellement les seules à pouvoir passer à l’échelle.

Entrées/sorties – La gestion des données et la performance des entrées/sorties vont fortement influencer la conception des applications. Mais ce sujet ne peut être abordé sous le seul angle technologique car la conception des applications demande des compromis entre traitements in-situ et traitements ex-situ, ainsi que sur le format des données, les stratégies d’accès et la relocalisation. Ces compromis ne sont pas uniquement du ressort des technologies et de la performance mais aussi de l’écosystème mis à disposition du chercheur.

Pour faire face au coût élevé de ces entrées/sorties, on peut les recouvrir par des calculs. En pratique, des cœurs de processeur sont uniquement dédiés au traitement des entrées/sorties afin de garantir une mise en œuvre totalement asynchrone des transferts de données et ainsi permettre de lisser les flux tout au long de la simulation. Cette technique dépend de l’architecture, de l’organisation des données et de la taille des différents espaces de stockage.

Intégration des pré- et post-traitements – Les pré- et post-traitements sont généralement les goulets d’étranglement des applications. Ils nécessitent un couplage particulièrement fin avec la partie calculatoire, qui tienne compte du cycle de vie complet des données. Plus particulièrement, une analyse in-situ peut permettre de réduire de manière très efficace le volume des données sortant de la machine [36, 37]. Cette technique doit cependant être bien pensée avec pour objectif l’élaboration d’une solution de long terme.

Architecture des codes – L’architecture des codes doit être maîtrisée et respectée. Des défauts dans sa conception ainsi que sa dissolution dans le temps ont des conséquences importantes. Par conséquent, les objectifs de l’architecture sont :

– d’améliorer la modularité des codes ;

– de faciliter la réécriture ou la modification des parties de code sujettes à évolution ;

– de faciliter la combinaison des technologies et des compétences des équipes de développement ;

-de fournir des interfaces externes qui améliorent l’utilisabilité ainsi que des interfaces internes qui structurent les développements et garantissent de bonnes pratiques ;

– de permettre le développement de versions très optimisées par rapport à la machine cible tout en conservant des versions génériques pérennes. L’architecture du code doit autoriser l’optimisation agressive des parties de code les plus calculatoires sans pour autant le déstructurer ;

– de permettre l’intégration de solveurs de type plug and play en fonction de la machine cible. Cet objectif va au-delà de la seule considération de l’architecture ; il est également lié aux schémas numériques de l’application.

Règles de codage – Les règles de codage, spécifiques à chaque application et chaque écosystème, doivent servir de guides aux développeurs. Leur but est de préserver l’efficacité du code et de faciliter sa maintenance et son évolution. Ces bonnes pratiques sont d’ailleurs bien développées dans la communauté Java qui les utilisent justement pour cela [38]. Dans le domaine HPC, ces règles doivent typiquement favoriser la vectorisation de code, la localité des données, une exécution parallèle efficace, etc. Les patterns de code sont une technique efficace pour y parvenir [39].

Bibliothèques – Les bibliothèques contiennent des fonctions de calculs courantes et très optimisées [40, 41]. Bien que leur utilisation soit recommandée, le choix d’une bibliothèque plutôt qu’une autre doit être mesuré pour, une fois de plus, garantir une certaine pérennité mais aussi pour assurer la disponibilité sur des systèmes variés ainsi que pour faciliter la construction des applications. Les bonnes pratiques au regard du choix des bibliothèques sont les suivantes :

– utiliser autant que possible les bibliothèques les plus optimisées telles que celles des constructeurs [42, 43], ou à défaut les bibliothèques Open source ;

– proscrire les bibliothèques conçues pour une exécution séquentielle et pour des systèmes sans hiérarchie mémoire (i.e. 1985 Numerical Recipes [44]) ;

– choisir des bibliothèques pérennes ou facilement remplaçables afin de limiter les incompatibilités avec d’autres plateformes.

Runtime – Les runtime (StarPU [45] [46], X-Kaapi [47], MPC [48]) offrent des accès aux ressources ainsi que des services qui ne sont pas directement fournis par les systèmes d’exploitation. De plus, le recours à des frameworks performants proposant un runtime robuste permet aussi d’offrir des capacités d’adaptation à la configuration de l’architecture cible. Par ailleurs, compte tenu du nombre croissant de threads qu’il faudra gérer dans le futur, l’utilisation d’ordonnanceurs hiérarchiques s’avèrera nécessaire afin d’éviter les surcoûts liés à la gestion de ces threads. En revanche, parce qu’ils sont moins précis, ces ordonnanceurs peuvent limiter la capacité à mettre en œuvre un équilibrage de charges fin.

Débogage – Généralement considéré comme une technique post-mortem, le débogage est difficile à intégrer dès le démarrage d’un projet [49]. Pourtant, la facilité de débogage dépend à la fois de la méthodologie de développement et de l’utilisation d’observateurs adéquats tels que les outils de traçage ou de visualisation des structures de données. Bien que dégradant fortement le temps d’exécution des applications, des utilitaires comme Valgrind [50] sont fortement recommandés.

Vectorisation et parallélisme de données – La vectorisation désigne ici l’utilisation d’instructions vectorielles telles qu’AVX, tandis que le parallélisme de données fait référence aux modèles présents dans les GPU (SIMT). Une bonne vectorisation contribue pour une large part aux performances des codes sur les CPU (80 % environ sur Xeon Phi, par exemple). L’exploitation de ce parallélisme ne doit cependant pas être laissée uniquement à la charge du compilateur. De bonnes règles de codage tout comme l’utilisation complémentaire de directives peuvent s’avérer très utiles pour aider le compilateur dans son travail de vectorisation. En revanche, l’utilisation d’intrinsèques n’est pas conseillée car ces derniers ne sont pas portables et rendent généralement le code plus difficile à lire et à maintenir.

Veille technologique – Anticiper les évolutions matérielles a un coût d’autant plus élevé que les voies sont multiples. Il faut avant tout éviter la tentation de tester tout ce qui est nouveau mais savoir agir à temps, sans procrastination. La veille technologique reste décisive pour faire de bons choix.

Réunir les compétences – Il faut solliciter au maximum les compétences nationales et internationales, comme la Maison de la Simulation par exemple, et les centres de compétences [51].

CONCLUSION

L’évolution des processeurs vers des architectures massivement parallèles pose des défis extrêmes en termes d’évolution des codes. Concevoir ou adapter un code dans l’objectif qu’il dure toute la prochaine décennie demande de nombreux choix à la fois technologiques et algorithmiques. Cet article présente un ensemble de recommandations pour aider à prendre les bonnes décisions. Il a aussi pour but de permettre d’appréhender les conséquences de ces choix afin de conserver une certaine flexibilité dans l’adaptation des codes.

[Références]

[26] Hudson – continuous integration.

[27] G. Grospellier and B. Lelandais. The arcane development framework. In Proceedings of the 8th Workshop on Parallel/High-Performance Object-Oriented Scientific Computing, POOSC ’09, pages 4:1–4:11, New York, NY, USA, 2009. ACM.

[28] M. Strembeck and U. Zdun. An approach for the systematic development of domain-specific languages. In Softw. Pract. Exper. 39(15):1253–1292, Oct. 2009.

[29] OpenACC Consortium. The OpenACC application programming interface. 2011.

[30] OpenMP Architecture Review Board. OpenMP application program interface version 3.0, May 2008.

[31] Boost.proto.

[32] W. Kirschenmann. Vers des noyaux de calcul intensif pérennes. PhD thesis, Université de Lorraine, 2013.

[33] L. Bautista-Gomez, S. Tsuboi, D. Komatitsch, F. Cappello, N. Maruyama, and S. Matsuoka. Fti: High performance fault tolerance interface for hybrid systems. In Proceedings of 2011 International Conference for High Performance Computing, Networking, Storage and Analysis, SC ’11, pp 32:1–32:32, New York, NY, USA, 2011. ACM.

[34] S. B. S. Di, L. B. Gomez, and F. Cappello. Optimization of multi-level checkpoint model for large scale hpc applications. In IEEE IPDPS, 2014.

[35] A. Moody, G. Bronevetsky, K. Mohror, and B. R. d. Supinski. Design, modeling, and evaluation of a scalable multi-level checkpointing system. In Proceedings of the 2010 ACM/IEEE International Conference for High Performance Computing, Networking, Storage and Analysis, SC ’10, pages 1–11, Washington, DC, USA, 2010. IEEE Computer Society.

[36] J. Bennett, H. Abbasi, P.-T. Bremer, R. W. Grout, A. Gyulassy, T. Jin, S. Klasky, H. Kolla, M. Parashar, V. Pascucci, P. P. Pébay, D. C. Thompson, H. Yu, F. Zhang, and J. Chen. Combining in-situ and in-transit processing to enable extreme-scale scientific analysis. In SC 2012, 2012.

[37] A. Mascarenhas, R. W. Grout, P.-T. Bremer, E. R. Hawkes, V. Pascucci, and J. H. Chen. Topological feature extraction for comparison of terascale combustion simulation data. In Topological Methods in Data Analysis and Visualization, Mathematics and Visualization, pages 229–240. Springer Berlin Heidelberg, 2011.

[38] JPL Java Coding Standard.

[39] T. Mattson, B. Sanders, and B. Massingill. Patterns for Parallel Programming. Addison-Wesley Professional, first edition, 2004.

[40] National Science Foundation and Department of Energy. BLAS, 2010.

[41] L. S. Blackford, J. Choi, A. Cleary, E. D’Azeuedo, J. Demmel, I. Dhillon, S. Hammarling, G. Henry, A. Petitet, K. Stanley, D. Walker, and R. C. Whaley. ScaLAPACK User’s Guide. Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, 1997.

[42] Intel Math Kernel Library.

[43] AMD core math library.

[44] W. H. Press, B. P. Flannery, S. A. Teukolsky, and W. T. Vetterling. Numerical Recipes – The Art of Scientific Computing. Cambridge: Cambridge University Press, 1986.

[45] Starpu handbook, 2013.

[46] S. Henry. Programming Models and Runtime Systems for Heterogeneous Architectures. PhD thesis, Université de Bordeaux 1, 2013.

[47] T. Gautier, F. Lementec, V. Faucher, and B. Raffin. X-Kaapi: a Multi Paradigm Runtime for Multicore Architectures. In Workshop P2S2 in conjunction of ICPP, page 16, Lyon, France, Oct. 2013.

[48] M. Pérache, H. Jourdren, and R. Namyst. Mpc: A unified parallel runtime for clusters of NUMA machines. In E. Luque, T. Margalef, and D. Benitez, editors, Euro-Par, volume 5168 of Lecture Notes in Computer Science, pages 78–88. Springer, 2008.

[49] K. Pouget. Programming-Model Centric Debugging for Multicore Embedded Systems. PhD thesis, Université de Grenoble, 2014.

[50] Valgrind.

[51] H2020-einfra-2015-1 – Centres of excellence for computing applications.

Navigation

<123>

© HPC Today 2019 - All rights reserved.

Thank you for reading HPC Today.

Express poll

Do you use multi-screen
visualization technologies?

Industry news

Brands / Products index