Il est hautement probable que vous soyez amené(e)s à réaliser un travail pratique avec une implémentation MPI de votre choix. En ce sens, la présente se veut une aide au démarrage, donc un premier programme donnant quelques pistes d'exploration et indiquant quelques pièges attendant les débutant(e)s avec un tel outil.
Par l'acronyme MPI, on entend Message Passing Interface, mais cela ne dit pas grand chose. Les serveurs MPI prennent en charge des exécutables respectant certaines règles structurelles (en particulier, qui sont liés avec les bibliothèques statiques et dynamiques appropriétes) et leur constituent un « monde » d'une certaine taille. Plusieurs implémentations MPI existent dans le monde; j'ai personnellement utilisé Deino MPI et Open MPI, et les deux m'ont donné de bons résultats, mais vous pouvez en explorer d'autres si le coeur vous en dit. Présentement, j'utilise MS MPI qui s'intègre bien à Visual Studio mais est très mal documenté.
C'est d'ailleurs pour apprivoiser ces pratiques que nous avons examiné en classe un moteur d'interopérabilité commercial (COM, mais ça aurait pu être CORBA, ICE, etc.), les concepts impliqués étant les mêmes.
À l'intérieur de ce monde, plusieurs processus communiquent en se passant des messages, sérialisés (par marshalling) de manière à ce que chaque homologue comprennent et manipulent un même format.
Les fonctions qu'offre MPI pour sérialiser les données primitives sont d'assez bas niveau mais rien ne vous empêche de construire une strate de proxy par-dessus celles-ci. Si vous comptez utiliser MPI ou une infrastructure semblable dans le futur, travailler avec un peu plus de soin aujourd'hui peut s'avérer profitable demain.
Pour explorer les fonctions de communication de MPI, MPI_Send() et MPI_Recv() sont de bons points de départ, ces fonctions étant simples à utiliser et bloquantes.
Il est toutefois possible que vous souhaitiez aller plus loin; je vous propose donc de consulter l'aide en ligne de l'API d'Open MPI (une implémentation MPI parmi plusieurs, mais c'est un site simple et bien fait) dont la plus récente version stable au moment d'écrire ces lignes est http://www.open-mpi.org/doc/v1.6/. Sachez toutefois que si jamais cette aide ne vous convient pas, il existe d'autres sources telles que http://mpi.deino.net/mpi/mpi_functions/
Il se trouve que l'approche MPI est surtout présente dans le monde du calcul réparti à très haute performance, ce qu'on nomme du High Performance Computing (HPC). À titre d'exemple, près de nous à Sherbrooke, les gens qui utilisent Mammouth le font souvent à travers MPI.
Les implémentations MPI que je connais sont axées vers les langages C et Fortran, mais supportent C++ et – à travers l'API C, dans la plupart des cas – bien d'autres langages. Je ne sais pas à quel point Java et les langages .NET sont supportés, mais si vous y tenez, il est toujours possible d'interfacer ces langages avec C ou C++ (je peux vous donner des trucs, incluant ceci).
Ce qui suit est un petit programme utilisant MPI avec C++ sous Visual Studio (2013), donc avec le compilateur MSVC. Si vous utilisez g++, notez que la documentation sera en général meilleure (le monde du HPC avec MPI est un monde très orienté Unix/ Linux) et vous n'aurez probablement pas besoin d'autant de soutien. Il est possible que le code source proposé ici vous intéresse malgré tout, étant indépendant du système d'exploitation ou du compilateur.
Parce que j'ai utilisé MS MPI, j'ai téléchargé un installateur puis ajouté les dossiers et les .lib requis à la configuration du projet. La conplexité de la configuration que vous aurez à faire dépendra de votre choix d'outils; lisez la documentation de votre produit de prédilection avec soin.
Raccourcis vers les diverses étapes a priori :
Ce qui suit est un bref des étapes par lesquelles je suis arrivé à une implémentation fonctionnelle. Vous pouvez vous y référer ou vous en inspirer si vous avez pris d'autres chemins pour en arriver à une solution qui vous ressemble. Notez que je peux vous aider de plusieurs manières mais que je ne connais pas tous les détails de tous les outils possibles et impossibles, alors ne vous fâchez pas si je n'ai pas immédiatement ce qu'il faut pour vous dépanner dans tous les cas.
Le code client pour MPI est assez portable d'une implémentation à l'autre, mais il demeure nécessaire d'avoir installé une implémentation ou l'autre de MPI au préalable.
Je souhaitais utiliser Boost.MPI, qui est une couche rendant l'utilisation de MPI nettement plus conviviale, mais je n'ai pas eu le temps de bâtir cette bibliothèque à travers mes autres tâches d'enseignement et d'étudiant au doctorat. Désolé! Cependant, si vous en avez envie (et si vous êtes moins coincés par le temps que moi), je suis certain que l'investissement initial requis pour mettre les outils en place peut rapporter gros.
Pour cette démonstration, j'ai fait un simple programme console natif; un projet vide de prime abord, le code des générateurs de code de Microsoft étant en général très mauvais pour le genre de programmation que je fais.
Pour assurer la bonne compilation du code avec Open MPI, j'ai ajouté aux répertoires standards de Visual Studio le chemin vers les fichiers d'en-tête qui accompagnent cette implémentation. Chez moi, à partir du menu contextuel Propriétés de la solution, Propriétés de configuration, Répertoires VC++, j'ai ajouté les répertoires pour les fichiers d'en-tête (Includes) et les bibliothèques (Libraries) appropriés. Il y aura des bibliothèques statiques à ajouter à la liste de celles utilisées par le projet, mais celles-ci varieront en fonction de ce que vous aurez utilisé.
Enfin, avec Open MPI, si vous compilez le code tel quel, vous aurez encore quelques erreurs. Il se trouve que Open MPI compile différemment si le symbole du préprocesseur OMPI_IMPORTS n'est pas défini, alors prenez soin de le définir (le plus simple est de le définir globalement en l'ajoutant la la liste que vous trouverez dans Propriétés de la solution, Propriétés de configuration, C/C++, Préprocesseur, Définitions du préprocesseur).
Enfin, selon les implémentations de MPI, il arrivera que le code client doive être lié dynamiquement à partir de quelques DLL. Étant donné que le système d'exploitation fouille entre autres dans les répertoires listés dans la variable d'environnement PATH lorsqu'il cherche des DLL, vous devrez peut-être ajouter à cette variable d'environnement le chemin requis.
Bien que vous puissiez développer un processus destiné à MPI avec g++ ou dans Visual Studio, on ne démarre généralement pas un tel processus par et pour lui-même. En effet, un processus MPI est typiquement lancé par MPI, qui contrôle son monde (la grappe toute entière) et assure la communication entre chacun des processus qui y sont impliqués.
Présumant que vous ayez compilé un programme reposant sur MPI sous Windows et que vous soyez à la ligne de commande dans le répertoire où se trouve l'exécutable généré, un lancement correct serait :
mpiexec -n 6 test_mpi.exe 3
Notez que mpiexec est le nom du serveur MPI de Microsoft, mais il se peut que vous ayez un autre nom de programme sous la main (p. ex. : mpirun). Peu importe. Ce qu'il faut lire ici est :
Ceci lancera donc six instances de test_ompi.exe en parallèle, et chaque main() recevra le paramètre " 3" . Notez que pour chacun, la taille de la grappe sera 6 mais le rang sera distinct (de 0 à 5 inclusivement).
Le code que j'ai écrit pour mon test est inspiré d'exemples pris dans Internet et légèrement adapté. Évidemment, la difficulté avec des outils comme une implémentation de MPI est rarement le code en soi; c'est plutôt la configuration de l'implémentation, règle générale, ce qui explique la section précédente.
Notez d'office que je présume ici que vous connaissez l'idiome de classe Incopiable.
Dans mon petit exemple, main() ne recevra pas de paramètres.
Remarquez tout d'abord que l'essentiel des fonctions clés de MPI se trouve déclaré dans l'en-tête <mpi.h>. Pour de la documentation sur MPI_Init(), voir http://www.open-mpi.org/doc/v1.6/man3/MPI_Init.3.php. Pour de la documentation sur MPI_Finalize(), voir http://www.open-mpi.org/doc/v1.6/man3/MPI_Finalize.3.php. J'ai utilisé ici la fonction MPI_Init() avec paramètres pour le chargement de MPI dans mon programme, mais je n'en tire pas vraiment profit. Vous trouverez une version sans paramètres de la même fonction dans MPI; à vous de voir laquelle conviendra le mieux à vos besoins. J'ai utilisé la notation entre crochets (réservée aux en-têtes jugés « standards ») du fait que j'ai pris soin de configurer mon outil de développement pour qu'il considère le répertoire des en-têtes de mon implémentation MPI comme en faisant partie. Étant donné qu'une implémentation MPI demande d'être chargée explicitement puis déchargée explicitement, j'ai appliqué l'idiome RAII pour automatiser cette symétrie (et alléger le code client) avec une classe MPI_Scope, et j'ai appliqué l'idiome de classe incopiable pour éviter toute tentative (volontaire ou accidentelle) de dupliquer une instance de cette classe (et de décharger deux fois l'implémentation par le fait-même). |
|
Cet exemple impliquera l'envoi et la réception de chaînes de caractères. J'ai encapsulé ces opérations dans des fonctions un peu simplistes, mais celles-ci montrent la syntaxe à l'utilisation de fonctions MPI. |
|
Examinons maintenant le (petit) programme principal proposé en exemple. Celui-ci :
Tel que mentionné plus haut, notre programme principal pourrait accepter des paramètres. Ceux-ci seraient captés par le gestionnaire MPI utilisé pour lancer le programme et relayés à tous les processus participants. |
|
Une exécution possible de ce programme serait :
mpiexec -n 4 "C:\Mr_Roy\Code\Code C++\IFT630\test_mpi\Release\test_mpi.exe"
Rang: 0, sur un total de 4 processus
0: Processus 1 au boulot
0: Processus 2 au boulot
0: Processus 3 au boulot
Remarquez qu'au démarrage, MPI est une implémentation particulière du modèle fork/join.
Si vous souhaitez d'autres références :