Calcul parallèle

Afin d’accélérer considérablement vos calculs, vous pouvez créer un programme qui utilise en parallèle plusieurs cœurs d’UCT au lieu d’un seul cœur. Selon le type de calcul, l’accélération pourrait évoluer linéairement en fonction du nombre de cœurs. Par exemple, sur une machine à quatre cœurs, votre programme pourrait rouler quatre fois plus vite. Lors de l’exécution sur les grappes disposant de centaines ou de milliers de processeurs, les avantages que le calcul parallèle offre peuvent s’avérer considérables.

Il existe plusieurs façons de faire un calcul parallèle : OpenMP, Python « multiprocessing » ou MPI.

OpenMP

L’une des manières les plus faciles de paralléliser un calcul est OpenMP. Il est disponible pour C, C++ et Fortran et il est inclus dans tous les compilateurs modernes.

Pour activer OpenMP sur les systèmes à mémoire partagée (par exemple sur un seul ordinateur), vous devez juste définir une option de compilation. Pour faire passer une boucle en parallèle, ajoutez une seule ligne de texte à côté d’une boucle « for ». La seule limite dans cette situation est le nombre de cœurs du processeur.

L’exemple suivant illustre une simulation du mouvement des particules, mises en parallèle au niveau physique.

Les positions des particules sont initialisées en utilisant le générateur de nombres aléatoires dans C++. Il s’agit d’une partie en série, car le générateur de nombres aléatoires n’est pas « thread-safe ».

ajout de la directive #pragma

La physique réelle se produit en mettant à jour de façon continue les positions de la particule. Par l’ajout d’une directive #pragma au-dessus de la boucle « for », cette partie peut être exécutée en parallèle.

ajout de la directive #pragma

La compilation de programmes utilisant OpenMP nécessite une option de compilation. Son nom dépendra du compilateur, mais celui-ci est en général -fopenmp ou -openmp. Par exemple, pour compiler l’exemple ci-dessus, utilisez :

g++ main.cpp -fopenmp

Pour plus de renseignements au sujet de la programmation avec OpenMP, regardez ces tutoriels (en anglais seulement).

L’exemple ci-dessus est relativement simple : il s'agit de l’exécution d’un programme en parallèle, car il n’y avait aucune interaction entre les particules.

L’interaction complique les choses parce que l'accès simultané par plusieurs cœurs aux positions des particules pourrait engendrer des effets indésirables. Il y a aussi des limitations en ce qui concerne l’accélération, car il y a toujours des temps système entraînés par le calcul parallèle. L’exemple ci-dessus n’est pas proportionnel de façon linéaire avec le nombre de « threads ». En fait, l’ajout de plusieurs « threads » pourrait ralentir le calcul. Par conséquent, il vaut mieux profiler un programme pour voir l’accélération réelle, surtout si vous envisagez une soumission au concours d’allocation des ressources auprès de Calcul Canada.

Un tel calcul avec un processeur Intel Xeon E5520 révèle qu’une accélération presque double est possible à l’aide de quatre « threads », mais il n’y a aucun gain lors de l’utilisation de plusieurs.

Speed-up as per number pf CPUs

Ce calcul a été généré en synchronisant l’exécution du programme à l’aide de différents « threads » en ajoutant la variable d’environnement OMP_NUM_THREADS.

La programmation en parallèle avec Python « multiprocessing »

Malgré que Python n’est pas naturellement orienté vers l’exécution multitraitement, il dispose quand même de plusieurs modules qui lui permettent de le faire. Le module de « multiprocessing » (en anglais uniquement) lance plusieurs processus Python pour utiliser plusieurs cœurs de processeur. Voici un exemple d’une simulation du mouvement aléatoire des photons dans un environnement de diffusion absorbant et isotrope.

Le multitraitement est mis en place lors de l’initialisation de « Manager » et en ajoutant les calculs à exécuter.

Simulate function

L’argument « target » devant « process » est le nom de la fonction qui fera exécuter le programme. La stimulation des photons lors de leur passage à travers l’environnement est faite avec la fonction « simulate ».

Simulate function

Notes :

  • La vérification de l’égalité entre « name » et « main » est nécessaire parce que le multitraitement lance plusieurs occasions pour Python de charger le script et exécuter tout code qui n’est pas protégé de cette façon.
  • Une tâche de l’outil I/O distinct est utilisée pour enregistrer les résultats sur le disque. La communication entre les processus vers l’outil se fait par « Queue ».
  • L’utilisation de trois cœurs donne une accélération de 270 % par rapport à un seul cœur sur Intel i5-4310U.
 


 

MPI

L’une des techniques courantes de parallélisation est « Message Passing Interface » (MPI), utilisée sur toutes les grandes grappes. MPI permet d’utiliser des centaines ou milliers d’UCT (ou bien plus) en combinant la puissance de calcul de plusieurs ordinateurs individuels qui communiquent sur le réseau. Ce réseau est généralement très rapide car il est souvent le facteur limitant les simulations à grande échelle. Des renseignements sont disponibles au sujet de Open MPI et MPICH (en anglais seulement).

Il est plus compliqué éd’écrire un programme avec MPI qu’avec OpenMP car il faut considérer  la transmission des données vers tous les nœuds de calcul. Par exemple, si la simulation comprend une grille, celle-ci doit être divisée en sous-grilles et les limites de ces dernières seront partagées par les différents processus à chaque étape. Cet exemple simule l’équation de la chaleur avec conditions à limites fixes.

La grille s’étend sur tous les processus MPI.

MPI processes

Pendant la simulation, chaque processus MPI doit communiquer avec ses voisins par des cellules « fantômes », ce qui assurera l’alignement des limites des grilles locales.

Ghost cells

Notes :

  • Un programme MPI commence toujours par « MPI_Init ».
  • Le nombre de processus impliqués dans les simulations est calculé avec « MPI_Comm_size ».
  • Le rang est le nombre de processus et sert à communiquer avec les autres processus et à exécuter le code par un seul processus unique. Le processus 0 fera écrire les données dans un fichier.
  • L’échange de cellules fantômes utilise « MPI_Send » et « MPI_Recv » pour transférer les limites des sous-grilles vers les sous-grilles voisines. Il est recommandé de réduire au minimum le transfert des données entre les processus pour exécuter la simulation à vitesse maximale.
  • Les données sont écrites uniquement par un seul processus afin d’éviter des conditions de compétition. Tous les autres processus envoient leurs sous-grilles pour traiter 0 à des fins d’écriture. Cela assure que les données restent séquentielles.
  • Un programme MPI se termine toujours par « MPI_Finalize ».
Autres méthodes

Il existe plusieurs solutions pour d’autres langages. Par exemple, Julia dispose d’un calcul parallèle (en anglais seulement) intégré. Au cas où une utilisation plus contrôlée est requise, il est possible d’utiliser « pthreads » (en anglais seulement) sur Linux. MPI est également disponible pour Python (en anglais seulement), pour R (en anglais seulement) et pour d`autres langues.

Pour une utilisation avancée, il est possible de combiner MPI au traitement « multithread » pour tirer avantage des systèmes à mémoire partagée tout en ayant la possibilité de combiner plusieurs systèmes.

Haut de page