11.10.1.3 : Parallélisation



À la manière de la vectorisation, différentes méthodes de parallélisation sont possibles. L'approche standard en calcul intensif consiste à utiliser les standards MPI pour la communication entre les nœuds de calcul et OpenMP pour le calcul multi-thread sur chaque nœud. Elle est relativement simple à mettre en œuvre dans des programmes peu abstraits (boucles sur des tableaux), mais peut devenir très complexe dès que la complexité du code et des données augmente.

Dans les langages qui permettent le développement d'abstractions sophistiquées, par exemple C++, Rust ou Scala, il est souvent plus pertinent de faire appel à des approches plus haut niveau~: bibliothèques d'algorithmes parallèles prêts à l'emploi, calcul distribué ayant l'apparence de la mémoire partagée, approches fonctionnelles et par flux de données, ou encore machine learning...

À ce niveau, le C++ rattrape petit à petit son retard sur les autres langages de programmation en intégrant à sa bibliothèque standard le support natif des threads depuis C++11 et un ensemble d'algorithmes parallèles standardisés depuis C++17. Il est prévu d'introduire une manière ergonomique d'utiliser ces algorithmes en C++20, et la bibliothèque HPX préfigure peut-être l'avenir du calcul distribué dans ce langage.

Une des difficultés majeure de la programmation parallèle est le débogage des applications. En effet, la gestion d'un nombre important de processus et de threads entraîne un très grand nombre de fluctuations, qui, lorsqu'elles ne sont pas prévues ou connues du développeur, peuvent se révéler extrêmement problématiques pour reproduire un bogue et comprendre la cause de celui-ci. Des logiciels de débogage permettent de gérer plusieurs threads sur une seule machine (comme gdb), mais au niveau d'un cluster de calcul, de nombreux développements sont encore en cours et doivent être suivis.

Une manière de simplifier la programmation parallèle est de ne pas la pratiquer dans toute sa généralité (manipulation arbitraire de mémoire partagée et échanges arbitraires de messages), mais de se cantonner à la place à un certain paradigme de programmation parallèle bien défini, comme celui de la programmation fonctionnelle. Il devient alors plus simple de raisonner sur le fonctionnement du programme. Mais le choix du paradigme doit être effectué avec soin, car tous les paradigmes ne s'appliquent pas à toutes les applications, et il est très difficile de vérifier le bon respect d'un paradigme sans utiliser un langage de programmation qui l'intègre nativement.