11.4.5.2 : Multi-threading en physique des particules



L'évolution des ressources matérielles de calcul va dans le sens d'une réduction de la quantité de RAM disponible par cœur CPU noteSans parler des GPUs, dont la capacité mémoire par cœur est très inférieure., alors que l'évolution des expériences de physique des particules au LHC va dans le sens d'une augmentation de la quantité de données et de la complexité des traitements par évènements. Les expériences LHC travaillant déjà à la limite du budget RAM qui leur est alloué par les centres de calcul, il est rapidement devenu clair que cette évolution conjointe allait conduire à une impasse si rien n'était fait pour l'empêcher.

Après avoir tenté pendant plusieurs années de retarder l'échéance par des solutions de contournement exploitant de manière hasardeuse le mécanisme de copy-on-write du système d'exploitation Linux noteSous Linux, juste après un appel système fork, les deux processus résultants partagent l'intégralité de leurs ressources mémoire. Celles-ci ne seront dissociées qu'au fur et à mesure que des écritures RAM surviendront. Il est possible d'abuser de ce mécanisme pour obtenir une forme simple de partage mémoire entre processus, mais cela n'est efficace que si l'appel à fork survient très tard dans l'exécution du programme et si celui-ci minimise ses écritures mémoires par la suite. Cette contrainte invisible complique l'écriture et la maintenance du code., les expériences LHC ont fini par accepter l'inévitable et engager un processus de parallélisation multi-thread de leur code afin de pouvoir plus facilement partager des ressources mémoires communes entre cœurs CPU et ainsi diminuer leur empreinte RAM.

Comme tout effort visant à paralléliser un code séquentiel de grande taille, cet exercice s'est révélé périlleux. En effet, un code séquentiel, qui n'a jamais été conçu pour une exécution parallèle va intégrer la supposition qu'il s'exécute de façon sérielle en profondeur, et peut avoir recours à un grand nombre de pratiques incompatibles avec le parallélisme.

Par exemple, un obstacle significatif a été l'utilisation intensive de variables globales ou de constructions conceptuellement équivalentes (pointeurs vers une donnée présents dans de nombreux endroits du code, variables statiques... ), ainsi que le recours fréquent à l'initialisation paresseuse noteProcédé consistant à n'initialiser une donnée que la première fois qu'un programme y accède. Dans un environnement parallèle, cette optimisation nécessite une synchronisation coûteuse en performance et n'est donc généralement pas souhaitable.. L'existence de mécanismes déclenchant silencieusement une entrée-sortie lorsqu'on se livre à ce qui semble être la lecture d'une donnée en RAM s'est aussi révélée problématique, puisque les entrées-sorties doivent être soigneusement encadrées dans un environnement parallèle.

Très rapidement, il est devenu apparent que l'exercice de parallélisation aurait pu être grandement simplifié par un meilleur respect des règles de l'art de l'ingénierie logicielle, qui tendent à décourager ce type de pratiques afin de garantir une plus grande intelligibilité du code. Hélas, la très grande majorité du code ayant été développé par des personnels contractuels (doctorants, post-docs... ) aujourd'hui disparus, et la main d'œuvre disponible pour la maintenance et l'évolution du code est aujourd'hui très faible. Il a été initialement jugé nécessaire de s'en tenir à des modifications aussi minimales que possible, et de ne pas procéder à une consolidation des fondations du framework avant d'effectuer la parallélisation.

Par la suite, cependant, la stratégie de parallélisation de différentes expériences a divergé. Certaines expériences, comme LHCb, ont fait le pari d'entreprendre un changement majeur de leur modèle de traitement d'évènements pour le rapprocher du paradigme de la programmation fonctionnelle, en gageant que l'important coût de développement lié à cette migration serait amorti par une bien plus grande facilité d'écriture du code par les physiciens. D'autres expériences, comme ATLAS, ont choisi à la place d'effectuer la migration vers une exécution multi-thread de façon aussi itérative que possible, en se concentrant sur la parallélisation des points chauds et en sérialisant tout le reste du calcul à grand renfort de mutexes.

Le pari de l'expérience LHCb s'est révélé payant, puisqu'ils disposent aujourd'hui d'un traitement d'évènements parallèle passant suffisamment bien à l'échelle pour leurs besoins actuels, là où les traitements de données de l'expérience ATLAS souffrent toujours de très graves problèmes de passage à l'échelle. Toutefois, ce pari, qui était indubitablement risqué, doit être replacé dans son contexte~: l'échéance de LHCb pour l'obtention d'une bonne parallélisation était le Run 3 du LHC, bien plus proche que le Run 4 visé par ATLAS. Et il est à ce stade possible qu'ATLAS parvienne à atteindre le niveau de parallélisme souhaité via leur approche plus prudente d'ici le Run 4.

Quoi qu'il en soit, le processus de parallélisation a été pour ces expériences une occasion de faire le point sur les outils de débogage et de profilage parallèle, leurs conclusions étant malheureusement en accord avec celles de ce document.

Et pour les deux expériences, l'objectif initial de la parallélisation, c'est-à-dire la réduction de l'empreinte mémoire, est aujourd'hui d'ores et déjà atteint puisque celle-ci se situe désormais autour de 150~Mo par thread ajouté en complément du thread principal, au lieu de plus de 2~Go par processus précédemment.

En conclusion, nous pouvons tirer de cette expérience les conclusions suivantes~:

  • À chaque fois que c'est possible, la parallélisation du code doit être envisagée dès sa conception, car elle pourra alors guider de nombreux autres aspects de son fonctionnement dans la bonne direction.
  • La parallélisation du code peut être facilitée par l'adoption de paradigmes appropriés, comme elle peut être compliquée par un faible niveau de qualité initiale du code. Lorsque c'est possible, commencer par une refonte du code visant à le simplifier et le clarifier simplifiera grandement l'exercice ultérieur de parallélisation.
  • S'il est facile de trouver de la main d'œuvre pour l'écriture d'un nouveau programme, le faible niveau de main d'œuvre stable alloué à la maintenance des infrastructures logicielles a des conséquences préoccupantes, au LHC comme ailleurs.
  • Si le développement itératif a souvent meilleure presse dans nos communautés, il ne faut pas négliger les bénéfices d'une approche plus disruptive lorsqu'un grand changement doit de toutes façons être effectué dans un logiciel.