Cours D'algo & Prog Parallèle
Cours D'algo & Prog Parallèle
Cours D'algo & Prog Parallèle
Objectifs du cours
Prérequis
Pour permettre une bonne compréhension de ce cours, les étudiants sont censés disposer des
connaissances solides dans les domaines et langages suivants :
1. Système d’exploitation Linux
2. Langage C (C++, Fortran, Java)
3. Notions de complexité
4. Gestion de la concurrence
Sommaire du cours
Chapitre 1
1.1.1. Motivation
Les besoins en puissance de calcul (voir même de stockage) vont en s’accroissant dans une
multitude de domaines (simulation et modélisation, traitement du signal, d’images, fouille de données,
conception de médicaments, biologie moléculaire, le classement des pages Web, etc.) et le
parallélisme est une tentative de réponse toujours d’actualité. C’est dans ce but qu’ont été conçus les
supercalculateurs, des machines composées de plusieurs centaines, voire milliers, de processeurs reliés
par un réseau d’interconnexion rapide. Ce type de machine a connu son heure de gloire jusque dans les
années 90. C’est alors que sont apparues les grappes de stations (ou cluster), bien moins coûteuses,
mais offrant des performances qui peuvent être comparables à celles des supercalculateurs. Ces
grappes sont constituées d’un ensemble de stations peu chères du commerce, connectées par un réseau
plus ou moins rapide. Cette révolution n’a été possible que grâce à des efforts constants en terme de
conception et de développement logiciel.
Actuellement, ces différents types de machines cohabitent et le parc des universités et des
entreprises est souvent très hétérogène, comprenant des supercalculateurs, des clusters (grappes de
calcul) et des stations de travail personnelles. Ces différentes machines ne suffisent cependant pas
toujours pour résoudre des problèmes de plus en plus complexes. Les réseaux étant de plus en plus
rapides, la tendance actuelle en matière de calcul parallèle et distribué est de chercher à fédérer un
ensemble de ces machines, réparties à l’échelle d’un continent, voire de la planète entière, afin d’en
agréger les puissances de calcul. C’est la fameuse grille de calcul (Grid computing) (figure 1.1). Ce
nouveau type de plate-forme de calcul est de nature très hétérogène, que ce soit au niveau des
ressources de calcul (processeurs) ou au niveau des capacités de communication (réseau). La prise en
compte de cette hétérogénéité est donc un enjeu majeur pour l’utilisation efficace des plates-formes
d’aujourd’hui et de demain.
Intuitivement, un travail peut être réalisé en beaucoup moins de temps s’il est réparti entre
plusieurs personnes ou sur plusieurs machines. Cette notion se nomme le parallélisme qui peut se
définir comme l’état de ce qui se développe dans la même direction ou en même temps.
Le parallélisme a été appliqué avec succès dans plusieurs activités humaines comme les
récoltes, la distribution du courrier, ou encore les chaînes de montage en usine. L’augmentation du
nombre de travailleurs permet de terminer plus rapidement. Une limite peut, bien sûr être atteinte, de
sorte qu’augmenter encore le nombre de travailleurs, n’apporte plus de gain de temps. En fait,
certaines tâches sont purement séquentielles et ne peuvent être exécutées que par une seule personne.
Par exemple, deux marathoniens ne peuvent se partager la distance à parcourir et réclamer la médaille
d’or.
C’est naturellement que la notion de parallélisme a été appliquée aux ordinateurs. De ce fait, il
a été possible de répondre aux besoins de puissance nécessaire à la réalisation de projets
consommateurs en temps de calculs et en taille mémoire. En fait, le parallélisme peut être défini
comme une technique d’accroissement des performances d’un système informatique fondée sur
l’utilisation simultanée de plusieurs ressources (processeur, mémoire, disque dur, …). Cela signifie
qu’il nécessitera le découpage du problème à résoudre en plusieurs sous-problèmes qui pourront être
résolus concurremment par plusieurs processeurs.
Un algorithme est un procédé de résolution de problème énoncé sous la forme d’une série
d’opérations à effectuer afin d’obtenir le résultat désiré. Le dictionnaire Webster’s Ninth New college
définit un algorithme comme « une procédure résolvant un problème mathématique en un nombre fini
d’étapes qui implique souvent la répétition d’une opération ; ou plus largement : une procédure
résolvant étape par étape un problème et aboutissant à une fin ». C’est un jeu de règles ou de
procédures bien définies qu’il faut suivre pour obtenir la solution d’un problème dans un nombre fini
d’étapes.
Un algorithme peut comprendre des procédures et instructions algébriques, arithmétiques,
logiques, et autres. Un algorithme peut être simple ou compliqué. Cependant, un algorithme doit
obtenir une solution en un nombre fini d’étapes.
Les algorithmes sont fondamentaux dans la recherche d’une solution par voie d’ordinateur,
parce que l’on doit donner à un ordinateur une série d’instructions claires pour conduire à une solution
dans un temps raisonnable. La programmation d’un ordinateur demande plus qu’une simple traduction
bien connue d’instructions en un langage compréhensible par l’ordinateur.
Actuellement, l’emploi du parallélisme est d’abord justifié par des besoins de performance
pour satisfaire d’une part, des contraintes de délai qui sont dues à la nature temps réel des problèmes
tels la prévision météorologique, l’interprétation de photographies satellites de régions critiques, etc…
et d’autre part par la taille des problèmes.
Citons également la nature intrinsèquement parallèle de certains problèmes qui gagneront en clarté à
être programmés selon un modèle parallèle offrant les constructions adéquates. Enfin, signalons le
besoin de puissance en tant que phénomène économique : une machine rapide sera plus vite
rentabilisée parce qu’elle réalisera plus de travail.
Les instances du corps de la boucle du problème (VP) peuvent s’exécuter indépendamment les
unes des autres :
Notons que dans le parallélisme de données, les données sont souvent plus nombreuses que le
nombre de processeurs. Ce qui pose le problème de répartition de charge entre les processeurs. Pour ce
faire, la répartition de données peut se faire de trois façons différentes : par bloc, cyclique ou par blocs
cycliques.
Ici le parallélisme est limité à trois processus. Les processeurs doivent se synchroniser à la fin
d’un calcul.
Le parallélisme de contrôle exige :
1. découpage du problème en tâches
2. étude des tâches pour déterminer celles qui peuvent s’exécuter en parallèle et celles qui doivent
s’exécuter séquentiellement.
Ces catégories de parallélisme peuvent être réalisées via les modèles de programmation
suivants :
- le modèle de programmation data-parallèle
- le modèle de programmation par processus légers1
- le modèle de programmation par passage de message
Dans le modèle de programmation data parallèle, le flot de contrôle est unique et chaque
instruction est exécutée sur un ensemble de données disposées sur une géométrie virtuelle. Cette
géométrie est définie par une description abstraite d'une grille de processeurs virtuelle. La complexité
de calcul et de communication d'un algorithme data-parallèle varie en fonction de la stratégie de la
distribution des données. Le choix de la distribution des données est en relation avec le choix de la
géométrie utilisée. En conséquence le choix de la géométrie devient un paramètre important à prendre
en compte dans ce type modèle de programmation. HPF et CM Fortran sont deux exemples typiques
des langages de programmation data parallèle. Ce modèle de programmation est bien adapté aux
machines SIMD comme CM-2 ou CM-200.
Le paradigme de programmation par processus légers est basé sur le partage des données dans
le même espace mémoire. Dans ce modèle de programmation les communications sont implicites. Les
informations sont transmises lors de l'écriture dans une zone de la mémoire partagée puis récupérées
quand un autre processus vient lire cette zone. La synchronisation, elle, doit être explicite en utilisant
des instructions élémentaires de gestion de l'exclusion mutuelle et d'attente sur condition. Les deux
outils les plus répandus de programmation par processus légers sont OpenMP et Posix Thread. Le
premier se base sur un compilateur qui analyse les directives spécifiques présentes dans le programme
et génère les appels à la bibliothèque de thread disponible. Le deuxième consiste à exploiter
1
multithread programming model
directement la bibliothèque thread défini dans la norme Posix. Ce modèle de programmation est bien
adapté aux machines MIMD de type mémoire partagée comme IBM SP3 ou IBM SP4.
Dans le modèle de programmation par passage de message, chaque processus possède sa propre
mémoire privée. Les processus doivent alors communiquer pour transférer de l'information à travers un
réseau2. Ainsi la communication est donc explicite alors que la synchronisation peut être explicite ou
implicite (elle est produite par la communication). C'est le dual du modèle à mémoire partagée. Ce
modèle de programmation est bien adapté aux machines MIMD qui ont une architecture à mémoire
partagée, mémoire distribuée et hiérarchique comme IBM SP3, IBM SP4, CRAY T3E, NOW ou les
cluster de IBM SP3 ou SP4. Ce modèle de programmation est le plus portable, donc le plus utilisé.
La difficulté de ce modèle provient de la caractéristique de réseau. Il existe plusieurs types de
communications selon le caractère bloquant ou non de l'émission et de la réception. On parle de
communications synchrones quand le transfert d'information n'est possible qu'après une
synchronisation globale des processus émetteur et récepteur. Dans ce cas l'émission et la réception
peuvent être bloquantes. L'émission peut être non bloquante si l'interface du réseau peut déplacer elle-
même les données, ou bien si le processeur peut être interrompu plus tard afin de déplacer les données.
Enfin, si l'émission est non bloquante avec un réseau ne pouvant conserver les messages, on obtient
une communication instable.
Selon Dekeyser et Marquet, on peut appeler langage à parallélisme de données tout langage
dans lequel une instruction implique plusieurs traitements identiques sur les données d’un ensemble.
Le parallélisme de données offre au programmeur des primitives définies sur des ensembles (vecteurs
et matrices le plus couramment), qui se traduisent par la duplication du traitement autant de fois qu’il y
a de données dans la structure spécifiée.
2
Les façons de transférer les données privées entre les processeurs sont différentes selon l'architecture de la machine parallèle
Si l’on peut trouver différentes façons de programmer une machine séquentielle, l’unicité du
processeur limite cependant les modèles qui pourraient lui être associés. Ainsi, le modèle « optimal »
est vite trouvé. Le parallélisme, au contraire, a très largement multiplié les possibilités :
• Nombre de processeurs,
• Agencement,
• Réseau de communication,
• Mémoire partagée ou répartie,
• Contrôle centralisé ou distribué,
• etc
Soit P=4 le nombre de processeurs. Imaginons que le temps d'exécution d'un programme soit
de 4 minutes s'il n'est traité que par un seul processeur.
Si on utilise les 4 processeurs pour réaliser ce même travail, chaque processeur devrait en
principe travailler 1 minute pour réaliser sa part du travail.
Si aucune communication n'est nécessaire, tous les processeurs ont effectivement fini leur tâche
après 1 minute. C'est le cas idéal: le wallclock time est 4 fois plus petit que celui de l'exécution sérielle.
Le temps cpu est cependant le même (4 processeurs ont travaillé pendant 1 minute: en tout il y a 4
minutes de travail).
Malheureusement certaines communications sont toujours nécessaires. Ces communications
rallongent le temps nécessaire à chaque processeur pour réaliser sa part du travail.
Chaque processeur travaillera donc plus qu'une minute (disons 1 min 15 sec). Même si on
utilise 4 processeurs, on n'obtient pas le résultat final en 4 fois moins de temps. Le wallclock time est
plus grand qu'un quart de celui de l'exécution sérielle. Par ailleurs le temps cpu a augmenté (4
processeurs ont travaillé pendant 1 min 15 sec: en tout il y a maintenant 5 minutes de travail).
Supposons que dans l'exemple précédent un des quatre processeurs soit deux fois plus lents que
les trois autres. Il mettra donc 2 min 15 sec pour réaliser sa part du travail. Les trois autres processeurs
auront travaillé 1 min 15 sec et seront ensuite restés 1 minute sans rien faire (cpu is idle). L'utilisateur
attend 2 min 15 sec pour avoir son résultat. Le wallclock time vaut donc ici plus de la moitié de celui
d'une exécution sérielle.
La situation est pire que si on avait travaillé seulement avec les trois autres processeurs. En
effet le wallclock time aurait été de 4 min / 3 + 15 sec = 1 min 35 sec. Pour utiliser exactement le
processeur le plus lent, il faut en fait lui donner deux fois moins de travail qu'aux trois autres. On
touche ici au problème de la répartition du travail (load balancing).
Où faut-il paralléliser? Au niveau le plus haut (traiter en parallèle les parties)? Ou au niveau le
plus bas (traiter en parallèle les sous-sous-parties)?
Paralléliser au niveau le plus haut réduit les temps de communication, mais le travail risque
d'être mal distribué si les parties n'ont pas toutes le même temps d'exécution.
Paralléliser au niveau le plus bas augmente les temps de communication, mais le travail est
mieux réparti entre les processeurs.
Nous recommandons de laisser le compilateur s'occuper des niveaux les plus bas et de
paralléliser son programme au niveau le plus haut.
Le parallélisme n’a pas que des avantages. Ainsi, nous pouvons retenir les dangers suivants liés
au parallélisme:
1. Les erreurs d'arrondis peuvent dépendre de la façon dont le travail est réparti. Les résultats d'un code
parallélisé ne sont pas toujours identiques à ceux d'un programme séquentiel.
2. Si plusieurs processeurs accèdent aux mêmes données, des effets indésirables peuvent survenir si ces
données sont modifiées par un processeur et si les autres continuent à travailler avec une ancienne
version de celles-ci.
On désigne par "cache coherency" le processus par lequel chaque processeur dispose à chaque
instant de la même copie de données partagées. Cette opération de mise à jour est gourmande en
communications et réduit l'efficacité de la parallélisation.
En pratique, il est préférable que chaque processeur travaille avec des versions privées de
chaque donnée (les variables privées ne sont pas visibles par les autres processeurs et il n'est donc pas
nécessaire de les informer d'une éventuelle modification de celles-ci).
3. L'ouverture de fichiers par plusieurs processeurs en même temps peut aussi poser des problèmes
(notamment lors d'une parallélisation avec OpenMP ou MPI). Il est alors nécessaire de tenir à jour une
variable partagée qui indique à chaque instant si un fichier est en cours de manipulation ou non. Un
processeur n'est alors autorisé à ouvrir un fichier que s'il est le seul à le faire.
Avant d'ouvrir un fichier, un processeur se doit donc de vérifier l’état de cette variable et
d'attendre qu'elle prenne une valeur lui indiquant qu'aucun autre processeur ne manipule de fichier. Il
change alors l'état de cette variable, manipule le fichier et remet la variable dans son état initial,
permettant ainsi aux autres processeurs de manipuler des fichiers.
4. Lorsque le travail d'un processeur demande que celui d'un autre soit terminé, il est à nouveau
nécessaire de le faire patienter.
1.8. Le top500
Tous les ans, une liste des 500 plus gros ordinateurs est publiée sur http://www.top500.org.
Extrait de la liste du 8 novembre 2006 :
Chapitre 2
Les problèmes liés au parallélisme peuvent être abordés tant d’un point de vue logiciel que
matériel :
1. Détermination de la concurrence (algorithme parallèle ou « software ») par évaluation de la
granularité (réfère le nombre de calculs, d’instruction ou d’opérations élémentaires), mise en
place de mécanisme de contrôle assurant l’exécution des programmes (synchronisation) et la
gestion des données (géométrie des communications).
2. Projection (« mapping ») des algorithmes parallèles sur des machines spécifiques (architectures
parallèles ou « hardware ») : complexité de l’entité de calcul élémentaire, mode opératoire,
répartition de la mémoire et réseau d’interconnexion.
Une machine parallèle est constituée d’un ensemble de processeurs capable de coopérer afin de
résoudre un problème.
Cette définition inclut les super-ordinateurs parallèles ayant des centaines ou des milliers de
processeurs, les grappes (clusters en anglais), c’est-à-dire un ensemble de machines (stations de travail
ou PC) reliées par un réseau d’interconnexion (RI), ou encore les stations de travail multiprocesseurs.
3
Symmetrical Multi Processors nommé aussi shared memory multi processors systems
4
nommé aussi shared nothing
Il existe aussi des architectures à mémoire non uniforme (NUMA5) et des architectures
hiérarchiques.
A l'inverse de l'architecture SMP, les architectures à mémoire non uniforme n'offrent pas un
coût uniforme d'accès à la mémoire. Ce type d'architecture permet d'avoir un adressage global pour les
mémoires distribuées (la mémoire locale de chaque processeur). Cet adressage global peut se situer
soit au niveau hardware soit au niveau logiciel. Chaque processeur qui possède sa propre mémoire
locale accède aux autres mémoires via un réseau d'interconnexion. Ce réseau peut être constitué d'un
ensemble hiérarchique de bus. Dans ce cas le temps d'accès à la mémoire dépend du nombre de bus
traversés. D'une manière générale, la bande passante de ce type de réseau augmente en fonction du
nombre de processeurs, permettant une meilleure extensibilité de l'architecture NUMA (quelques
centaines de processeurs) comparativement à l'architecture SMP. Les machines SGI origine 2000,
Onyx 3000, Compaq GS Series Alpha Server sont les exemples représentatifs de l'architecture NUMA.
Une architecture de type hiérarchique est une combinaison de l'architecture à mémoire partagée
et de l'architecture à mémoire distribuée. L'idée est ici de construire une machine à mémoire distribuée
dont chaque noeud adopte une architecture parallèle à mémoire partagée. L'architecture de type
hiérarchique permet d'obtenir l'extensibilité d'une architecture à mémoire distribuée en gardant une
5
Non Uniform Memory Acces
partie de la flexibilité apportée par la mémoire partagée. Elle permet l'interconnexion d'un grand
nombre de noeuds à mémoire partagée, pouvant eux-mêmes être de puissants multiprocesseurs. Dans
chaque noeud les communications entre les processeurs se font efficacement grâce à la mémoire
partagée.
Le routage détermine le chemin à prendre dans le réseau pour aller d’un processeur source à un
processeur destination. Le mode de communication indique la façon dont les messages sont
acheminés.
On distingue les modes suivants :
- Commutation de circuits : avant d’envoyer son message, la source envoie une requête
jusqu’à la destination afin de construire un circuit physique de bout-en-bout. Une fois le
circuit établi, le message est alors transmis directement jusqu’à la destination,
- Commutation de paquets : chaque routeur dispose de tampons mémoires, chacun pouvant
stocker un paquet. Lors de la réception d’un paquet, le routeur le stocke dans un des
tampons avant de le retransmettre au routeur suivant sur la route du paquet. Chaque paquet
contient des informations permettant au routeur de calculer le chemin à suivre,
- « Whormhole » : le message est découpé en petites entités appelées « flits » qui sont
stockées dans les tampons mémoires des routeurs. Seul le premier « flit » contient des
informations permettant au routeur de calculer le chemin à suivre, tandis que les autres
« flits » ne contiennent que des données. Ils doivent donc avancer les uns à la suite des
autres dans le réseau.
Historiquement, les premières machines parallèles sont des réseaux d'ordinateurs, et des
machines vectorielles et faiblement parallèles (années 70 - IBM 360-90 vectoriel, IRIS 80
triprocesseurs, CRAY 1 vectoriel …).
1. Machine SISD. Une machine SISD (Single Instruction stream Single Data) est ce que l'on
appelle d'habitude une machine séquentielle, ou machine de Von Neuman. Une seule
instruction est exécutée à un moment donné et une seule donnée (simple, non-structurée) est
traitée à chaque instant.
2. Machine MISD. Une machine MISD (Multiple Instruction stream Single Data) peut exécuter
plusieurs instructions en même temps sur la même donnée. Cela peut paraître paradoxal mais
cela recouvre en fait un type très ordinaire de micro parallélisme dans les microprocesseurs
modernes: les processeurs vectoriels et les architectures pipelines.
3. Machine SIMD. Dans une machine SIMD (Single Instruction stream Multiple Data) chaque
instruction d'un programme est exécutée de façon identique sur chaque processeur, mais sur des
données différentes et privées à chaque processeur. Autrement dit, dans ce type de machine,
l'exécution en parallèle de la même instruction se fait en même temps sur des processeurs
différents. En général elle possède un grand nombre de processeurs. Les calculateurs CM-200,
CM5, Hitachi S3600, CPP DAP Gamma II et Alenia Quadrics furent parmi les premiers
exemples de partie de machines SIMD.
Les machines systoliques sont des machines SIMD particulières dans lesquelles le
calcul se déplace sur une topologie de processeurs, comme un front d'onde, et acquiert des
données locales différentes à chaque déplacement du front d'onde (comportant plusieurs
processeurs, mais pas tous en général).
Dans les deux cas, l'exécution en parallèle de la même instruction se fait en même temps sur
des processeurs différents (parallélisme de donnée synchrone).
4. Machine MIMD. Le cas des machines MIMD (Multiple Instruction stream Multiple Data) est
le plus intuitif. Ici, chaque processeur peut exécuter un programme différent sur des données
différentes. On a plusieurs types d'architecture possibles:
Parce qu'il n'est en général pas nécessaire d'utiliser des programmes différents pour chaque
processeur, on exécute souvent le même code sur tous les noeuds d'une machine MIMD mais ceux-ci
ne sont pas forcément synchronisés. On parle alors de modèle SPMD (Single Program Multiple Data).
Une machine MIMD à mémoire partagée est principalement constituée de processeurs avec des
horloges indépendantes, donc évoluant de façon asynchrone, et communicant en écrivant et lisant des
valeurs dans une seule et même mémoire (la mémoire partagée). Une difficulté supplémentaire, que
l'on ne décrira pas plus ici, est que chaque processeur a en général au moins un cache de données, tous
ces caches devant avoir des informations cohérentes aux moments cruciaux.
Notons SPMD est un cas particulier du modèle plus général MPMD (Multiple Program,
Multiple Data), qu’il peut d’ailleurs émuler.
L’algorithmique parallèle est une approche qui a permis de développer des modèles permettant
de résoudre un grand nombre de problèmes. On peut classifier ces modèles en deux grandes familles
que sont : les modèles à grain fin et les modèles à gros grain.
Les modèles à grain fin ont été les premiers modèles parallèles à apparaître. La notion de grain
fin vient du fait que, pour ces modèles, on suppose que le nombre de processeurs est essentiellement
égal au nombre de données d’entrée.
Les deux grandes familles de modèles à grain fin sont : les modèles de machines à mémoire
partagée et les modèles de machines à mémoire distribuée.
Ce modèle est davantage spécifié en définissant le mode d’accès à la mémoire. Quatre variantes
sont les plus souvent utilisées :
- Exclusive-Read Exlusive-Write (EREW) PRAM : dans cette variante, deux processeurs ne
peuvent avoir accès au même emplacement mémoire simultanément que ce soit pour lire ou
bien pour écrire,
- Concurrent-Read Exclusive-Write (CREW) PRAM : cette variante alloue plus d’un
processeur pour lire mais pas pour écrire dans le même emplacement mémoire en même
temps,
- Concurrent-Read Concurrent-Write (CRCW) PRAM : pour cette variante, il est possible
pour plusieurs processeurs d’avoir accès au même emplacement mémoire pour lire ou pour
écrire,
- Exclusive-Read Concurrent-Write (ERCW) PRAM : cette variante a un nombre très
restreint d’applications.
L’avantage de ce modèle est sa simplicité. Il est, de plus très utile pour dégager le parallélisme
des problèmes étudiés. C’est souvent une première étape pour la parallélisation.
Etant donné son haut niveau d’abstraction, il permet de savoir dans quelle mesure un problème
peut être parallélisé ou non. Par contre, il est fortement éloigné des machines réelles. De plus, les
contraintes technologiques ne permettent pas à un grand nombre de processeurs d’accéder à une
mémoire commune en temps constant. C’est pourquoi il n’existe plus de machine PRAM à ce jour ; à
la place, la mémoire est distribuée entre les processeurs.
Dans ce modèle, chaque processeur a sa propre mémoire locale de taille constante et il n’existe
pas de mémoire partagée. Les processeurs peuvent seulement communiquer grâce à un RI. Comme en
PRAM, les processeurs travaillent de manière synchrone. A chaque étape, chaque processeur peut
simultanément envoyer un mot de données à un de ses voisins, recevoir un mot de données d’un de ses
voisins et effectuer une opération locale sur ses données.
- le degré, qui est le nombre maximal de voisins pour un processeur. Il correspond à une
sorte de limitation architecturale donnée par le nombre maximum de liens physiques
associés à chaque processeur,
- le diamètre, qui est la distance maximale entre deux processeurs (la distance étant le plus
court chemin dans le réseau d’interconnexion entre ces deux processeurs). Il donne une
borne inférieure sur la complexité des algorithmes dans lesquels deux processeurs
arbitraires doivent communiquer,
- la bissection, qui est le nombre minimum de liens à enlever afin de déconnecter le réseau en
deux réseaux de même taille (à plus ou moins un processeur près). Elle représente une
borne inférieure sur le temps d’exécution des algorithmes où il existe une phase qui fait
communiquer la moitié des processeurs avec l’autre moitié.
Parmi ces modèles à mémoire distribuée, la grille à deux dimensions et l’hypercube ont été
beaucoup utilisés. Plus récemment d’autres réseaux d’interconnexion sont apparus tels que le
honeycomb et son extension appelé star-honeycomb, ou bien encore le Xmesh.
La spécificité des algorithmes développés sur les modèles à mémoire distribuée (si possible
efficaces voire optimaux) est un inconvénient majeur. Ainsi, ces algorithmes ne sont pas portables car
ils sont fortement dépendants de la topologie choisie.
c. Modèle systolique
Par définition, un réseau systolique est un réseau de processeurs qui calculent, échangent des
données régulièrement. L’analogie est faite avec la régularité de la contraction cardiaque qui propage
le sang dans le système circulaire du corps. Chaque processeur d’un réseau systolique peut être vu
comme un cœur jouant un rôle de pompe sur plusieurs flots de traversant. Le rythme régulier de ces
processeurs maintient un flot de données constant à travers tout le réseau.
Ce modèle apparaît lorsque les technologies VLSI et WSI ont émergées et sa réussite tient en
grande partie à son adaptation entre la demande de calculateurs extrêmes rapides à faible coût et la
possibilité de réduire très significativement le temps d’exécution de nombre d’algorithmes séquentiels
au simple prix d’un accroissement de la complexité du matériel par réplication de structures
élémentaires simples et régulières.
Dès lors, d’importants efforts ont été fournis pour concevoir des architectures parallèles basées
sur la régularité de la rythmique des données (« dataflow architectures »), pour pipeliner les
processeurs ou les vectoriser, ou sur la régularité du flot d’exécution (« waveflont architectures »). Le
problème principal du développement de tels ordinateurs réside dans l’organisation des flots intensifs
de contrôle et de données assurant de hautes performances. Les architectures pipelinées et
« wavefront » sont des variantes systoliques qui sont parmi les plus efficaces. Elles ont ainsi largement
contribué à la diffusion du modèle systolique et à sa familiarisation.
Les caractéristiques dominantes d’un réseau systolique peuvent être résumées par un
parallélisme massif et décentralisé, par des communications locales et régulières et par un mode
opératoire synchrone. Pour décrire un réseau systolique, il est donc nécessaire, tout comme avec un
langage tel que VHDL (langage normalisé de spécification pour la réalisation de circuits intégrés), de
spécifier :
- le graphe d’interconnexion des processeurs (topologie du réseau),
- l’architecture d’un processeur (description des registres et canaux : nom, type, sémantique
…),
- le programme d’un processeur (lecture des valeurs sur les canaux d’entrée, combinaison
d’opérateurs arithmétiques et logiques, mémorisations des résultats dans les registres et
écritures sur les canaux de sortie),
- le flot de données consommées par le réseau pour produire une solution.
La plupart des machines utilisées actuellement sont à gros grain de données, c’est-à-dire que la
taille de chaque mémoire locale est beaucoup plus grande que la taille d’une donnée. Ainsi, de
nombreux travaux ont permis de décrire des modèles prenant en compte les caractéristiques réelles des
machines. Ces modèles reconnaissent, par exemple, l’existence de coût de communication,
contrairement au modèle PRAM, sans pour autant spécifier la topologie du support de
communications, contrairement aux modèles à grain fin de mémoire distribuée.
Le modèle BSP (« Bulk Synchronous Parallel »), premier modèle du genre proposé par Valiant,
formalise les caractéristiques architecturales des machines existantes au moyen de quatre paramètres.
Le modèle LogP décrit par Culler et al. spécifie plus de paramètres que BSP, tandis que CGM
(« Coarse Grained Multicomputers ») proposé par Dehne et al. est une simplification de BSP.
Notons que le thread ou processus est en quelque sorte une copie du programme.
Exemple
1. S=f(A[1])+ . . . + f(A[n])
2. Décomposition
• Calcul de chaque f(A[j])
• Parallélisation de n éléments, où n peut >>p
• Calcul de la somme S
3. Assignation
• Thread k somme Sk = f(A[k*n/p]) + … + f(A[(k+1)*n/p-1])
• Thread 1 somme S = S1 + … + Sp
• Thread 1 communique S aux autres threads
4. Orchestration
• Démarrage des threads
• Communication, synchronisation avec le thread 1
5. Mapping
• Processeur j exécute le thread j
Dans cette section, nous présentons par ordre croissant de complexité, les différentes manières
de paralléliser son problème.
2. Evitez d'encombrer vos boucles avec des instructions qui peuvent laisser le compilateur penser que
les itérations ne sont pas indépendantes. Par exemple, évitez ceci:
6
A condition d’utiliser les options d’autoparallélisation au moment de la compilation
Si votre parc informatique vous permet de lancer plusieurs exécutables en même temps, vous
pouvez paralléliser votre problème en confiant à chaque machine les calculs correspondant à une partie
de ce problème. L'idée ici est de paralléliser la tâche à réaliser plutôt que le programme lui-même. Ce
dernier en effet ne doit subir aucune modification.
Notons que l’affectation des tâches aux processeurs pose un problème d’ordonnancement.
Exemple:
Vous disposez de 20 machines et vous devez calculer une fonction sur une grille de 100 points.
Il suffit de confier à chaque machine le calcul de 5 points. Plutôt que paralléliser votre programme,
l'idée ici est de planifier 20 tâches qui exécuteront le même programme séquentiel sur des inputs
différents. Une fois les 20 tâches terminées, il vous suffit de collecter
les résultats partiels.
Comment faire si toutes les machines ne sont identiques? Il faut éviter de confier aux machines les
plus lentes trop de travail sous peine de devoir finalement les attendre. L'idée est de confier à chaque
machine la bonne quantité de travail, afin qu'elles terminent toutes en même temps.
Pour fixer les choses, supposons que nos ayons 10 machines supplémentaires, 3 fois plus lentes
que les 20 premières. Nous avons 100 calculs à réaliser, qui prennent une minute chacun sur les
machines les plus rapides et donc 3 minutes sur les plus lentes.
Soient N=100 le nombre de calculs à réaliser, N1=20 le nombre d'ordinateurs rapides, N2=10 le nombre
d'ordinateurs lents. Appelons v1=3 la vitesse des machines rapides et v2=1 la vitesse des machines
lentes. On cherche le nombre n1 de calculs à confier à chaque machine rapide et le nombre n2 de
calculs à confier à chaque machine lente.
n1.N1 + n2*N2=N
n1/v1= n2/v2
On trouve :
v1.N 3.100
n1= = = 4.2857...
v1.N1+v2.N2 3.20+10
v2.N 1.100
n2= = = 1.4285...
v1.N1+v2.N2 3.20+10
Toutes les machines termineront leur tâche en même temps si on leur confie à chacune ce
nombre de calculs. Comme ces nombres sont fractionnaires, certaines des machines lentes recevront en
réalité 1 calcul à réaliser, d'autres 2. Ces dernières reçoivent donc trop de travail et se feront attendre à
la fin du processus. Il leur faudra en effet 2 * 3 = 6 minutes pour accomplir leur tâche, alors que 5
minutes suffisaient en ne travaillant qu'avec les machines les plus rapides. On voit que ce modèle
donne une approximation du nombre de calculs à confier à chaque processeur, mais ne donne pas le
nombre optimal.
Dans l'exemple précédent, il aurait fallu ne donner qu'une seule tâche aux processeurs les plus
lents (n2=1) et laisser ce qui reste aux ordinateurs les plus rapides. La nouvelle idée est de ne confier
une tâche à un processeur lent qu'à partir du moment où tous les processeurs rapides ont déjà reçu 3
tâches. Ce n'est qu'ainsi que les processeurs lents deviennent utiles et cessent de se faire attendre à la
fin de la procédure.
La distribution initiale des tâches se fait alors au compte-gouttes (cfr figure 2.6). Les v1.N1=60
premiers calculs sont confiés aux 20 processeurs rapides. Les v2.N2=10 calculs suivants sont alors
confiés au 10 ordinateurs lents. Et on recommence ainsi: les v1.N1=60 calculs suivants sont confiés aux
ordinateurs rapides. On ne confie un deuxième calcul aux ordinateurs lents que si chaque ordinateur
rapide a déjà reçu 6 tâches (il s'agira des tâches 131-140)
o en revenant à la première case si cette condition n'est pas vérifiée ou si on arrive tout à
droite
Si les processeurs qu'on souhaite utiliser ont accès à une zone mémoire commune (shared
memory), on peut paralléliser son programme avec OpenMP. La librairie OpenMP permet de donner
au compilateur des directives sur la manière de paralléliser son programme. Par exemple, on peut lui
indiquer que telle boucle est parallélisable, que telle variable est partagée ou privée, etc. Le
compilateur prend en charge toute la gestion de ces informations.
Les standards d'OpenMP datent de 1997, ceux d'OpenMP-2 de 2000. Les développeurs de
compilateurs (Fortran, C et C++) les implémentent.
Le dernier modèle est celui où les processeurs n'ont pas accès à une zone mémoire commune,
mais possèdent uniquement une mémoire privée, visible par eux seul (distributed memory). Les
processeurs doivent communiquer explicitement entre eux pour se transmettre des informations. La
librairie MPI (Message Passing Interface) permet de contrôler toutes ces communications. Nous
l'approfondirons au chapitre 3.
Pour étudier la complexité parallèle, on considère des classes d’architectures parallèles très
simples qui peuvent être caractérisées par un seul paramètre, P le nombre de processeurs. La
complexité parallèle est alors dépendante de deux variables : la taille N du problème et le nombre de
processeurs utilisés.
On définit :
a) Le gain ou la performance (speedup) comme le rapport entre le temps d’exécution en mode
séquentiel et le temps d’exécution en parallèle sur P processeurs.
b) L’efficacité (efficiency) comme le rapport entre le gain et le nombre de processeurs utilisés.
Le travail d’un algorithme parallèle est défini comme le produit de la complexité en temps d’un
processeur par le nombre P de processeurs.
Indépendamment des problèmes traités jusque maintenant, l'efficacité d'une parallélisation est
limitée par le fait qu'un programme ne comporte pas que des parties parallélisables.
Démonstration:
Interprétation:
Cas intermédiaires:
• Si on tient compte des temps de communication: S(P), E(P) [( P) || ( )] 0 (il existe un P
optimal).
Conclusions:
• S(P) finit par saturer (d'autant plus tard que f est proche de 1).
Faire un calcul parallèle, c’est faire coopérer plusieurs processeurs pour réaliser un calcul.
Avantages:
1. Rapidité:
Pour N processeurs, temps de calcul divisé par N, en théorie.
2. Taille mémoire:
Pour N processeurs, on dispose de N fois plus de mémoire (en général)
Difficultés:
Il faut gérer le partage des tâches.
Il faut gérer l'échange d'information (tâches non indépendantes)
Chapitre 3
Programmation parallèle avec MPI
3.1 Introduction
Le modèle de parallélisme considéré dans ce chapitre est celui où les processeurs disposent
uniquement d'une mémoire privée, visible par eux seul (distributed memory). Il n'y pas ici de mémoire
partagée et tout échange d'information entre les processeurs se fait via des messages qui doivent être
programmés explicitement. La librairie MPI (Message Passing Interface) fournit les instructions qui
permettent de programmer ces échanges d'information.
Un message est constitué de paquets de données (variables, scalaires, tableaux, etc) transitant
du processus émetteur au(x) processus récepteurs(s). Dans cet environnement, si message est envoyé à
un processus, celui-ci doit ensuite le recevoir (voir figure 3.2). Le processus récepteur doit pouvoir
classer et interpréter les messages qui lui ont été adressés.
Dans certains modèles de programmation, on confie parfois à un thread particulier (le Maître,
qui a le numéro 0) la tâche de distribuer/collecter les données. Les autres threads (les Esclaves)
réalisent le travail.
2. Messages asynchrones: La communication se termine dès que le message est envoyé. L'émetteur
sait juste quand le message est parti.
4. Messages non bloquants: L'émetteur peut réaliser d'autres tâches durant l'envoi du message.
Opérations:
• Barrière: attendre que tous les processeurs arrivent au même point du programme
• Réduction: on prend des données chez chaque processeur, on les réduit à une seule donnée,
qu'on met ensuite à disposition de tous les processeurs
Lorsqu'on utilise des messages bloquants sans précaution, il peut arriver que tous les threads se
retrouvent dans un état d'attente perpétuel (deadlock).
Exemple: Si tous les threads envoient en même temps un message bloquant: ils se retrouvent tous à
attendre une confirmation de bonne réception de leur message alors qu'aucun d'eux n'est capable de
l'intercepter.
# include<stdio.h>
{
int myid, numprocs;
/*Initialisation de MPI*/
MPI_Init (&argc,&argv);
MPI_Comm_Rank (MPI_COMM_WORLD,&myid) ;
MPI_Comm_Size (MPI_COMM_WORLD,numprocs);
if (myid == 0)
/*Quitter mpi*/
MPI_Finalize ();
Résultat:
Une communication dite point à point a lieu entre deux processus, l’un appelé processus
émetteur et l’autre processus récepteur (ou destinataire).
MPI_STATUS_SIZE status;
...
où data est l'information à transférer, size sa taille et type son type. source et destination sont les numéros
de l'émetteur et du récepteur. tag est le numéro ou étiquette du message. Le tableau status
(MPI_STATUS_SIZE) permet de savoir par la suite si la communication est terminée.
où request est un entier qui permet de savoir par la suite si la communication est terminée.
• Envoi bufferé bloquant: le message est stocké dans une mémoire tampon avant d'être envoyé
MPI_BUFFER_ATTACH(buffer, size)
MPI_BSEND(data, size, type, destination, tag, MPI_COMM_WORLD)
MPI_BUFFER_DETACH(buffer,size)
où buffer est la variable tampon dans laquelle data est transférée avant la communication.
• Réception bloquante:
où source est le numéro de l'émetteur (ou MPI_ANY_SOURCE) et tag est le numéro du message
(ou MPI_ANY_TAG).
où request est un entier qui permet de savoir par la suite si la communication est terminée.
MPI_WAIT(request,status)
MPI_TEST(request,flag,status)
# include<studio>
# include<mpi.h>
int main(int argc, char *argv[])
{
int rank, size;
MPI_Status status;
char SendMsg[23]="Salut du processeur 0";
char RecvMsg[23];
int root;
int tag;
int rankdest;
int i;
/* initialization */
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
/* envoi du message */
root = 0;
if (rank==root)
for(i=1;i<size;i++)
{
tag=i;
rankdest=i;
MPI_Send(SendMsg,23,MPI_CHAR,rankdest,tag,MPI_COMM_WORLD);
}
/* reception du message */
RecvMsg=SendMsg;
if(rank!=root) {
tag = rank;
MPI_Recv(RecvMsg,23,MPI_CHAR,root,tag,MPI_COMM_WORLD,&status);
printf(" je suis le processeur %d, j'ai recu le message : %s \n",rank,RecvMsg);
}
MPI_Finalize();
}
MPI_WAITALL(count,array_of_requests,array_of_statuses)
MPI_TESTALL(count,array_of_requests,flag,array_of_statuses)
MPI_WAITANY(count,array_of_requests,index,status)
MPI_TESTANY(count,array_of_requests,index,flag,status)
Pour attendre que tous les threads arrivent à un endroit donné du programme:
MPI_BARRIER(MPI_COMM_WORLD)
Pour distribuer une information du thread source à tous les autres threads, on peut utiliser:
Cette instruction doit être exécutée par tous les threads (i.e., elle sert à envoyer-recevoir).
# include<stdio.h>
# include<mpi.h>
int main(int argc, char *argv[])
{
int rang, valeur;
/* initialization */
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&rang);
/* envoi du message */
if (rang==2)
{
valeur=rang+1000;
MPI_BCAST(valeur,1,MPI_INT,2,MPI_COMM_WORLD);
}
printf(" Moi, processeur %d, j'ai reçu %d du processeur 2 \n",rang,valeur);
MPI_Finalize();
}
Execution:
Pour scinder une information détenue par le thread source et distribuer ses parties aux autres
threads, on peut utiliser:
où data_to_send est l'information à distribuer et size_to_send est la taille des morceaux à envoyer à chaque
thread. Ces morceaux sont placés par chaque thread dans data_to_recv, dont la taille est size_to_recv.
type_to_send et type_to_recv décrivent les types de messages. Si type_to_send = type_to_recv, alors size_to_send
= size_to_recv.
L'opération contraire consiste à rassembler les fragments détenus par chaque thread pour
donner au thread source l'information complète:
où data_to_send est le morceau d'information contenu par chaque thread, size_to_send sa taille,
data_to_recv la variable qui contiendra tous les morceaux, size_to_recv la taille des morceaux reçus.
Si type_to_send = type_to_recv, alors size_to_send = size_to_recv.
3.5.5. Rassembler chez chaque thread les fragments d'une information (allgather)
Pour rassembler chez chaque thread les fragments d'une information, on peut utiliser:
Pour donner à chaque thread i la ième partie de chaque fragment, on peut utiliser cette sous-
routine:
MPI_COMM_WORLD)
Il est possible de réaliser certaines opérations sur des variables réparties entre les différents
threads. Pour que le résultat de cette opération soit communiqué au thread source on peut utiliser cette
sous-routine:
où data_to_send est l'information contenue par chaque thread sur laquelle l'opération operation doit être
exécutée. Le résultat apparaît dans la variable data_to_recv du thread source.
Les principales opérations de réduction prédéfinies (il existe aussi d’autres opérations logiques)
sont résumées dans le tableau ci-dessous.
3.6.2. Opérations de réduction avec résultat communiqué à tous les threads (allreduce)
3.6.3 Opérations de réduction avec résultats partiels communiqués aux différents threads (scan)
Avec la sous-routine scan, chaque thread reçoit le résultat de l'opération effectuée sur son
élément et celui des threads qui le précèdent.
Rappel : la trace d’une matrice est la somme des éléments de sa diagonale (matrice
nécessairement carrée)
Immédiatement, on voit facilement que le problème peut être parallélisé en calculant la somme
des éléments diagonaux sur plusieurs processeurs puis en utilisant une réduction pour calculer
la trace globale
#include <stdio.h>
#include <mpi.h>
double A[N][N];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
MPI_Comm_size(MPI_COMM_WORLD, &np);
tranche = N/np;
/* … */
if (me == 0) {
buffer[i] = A[i][i];
trace_loc = 0;
trace_loc += diag[i];
if (me == root)
MPI_Finalize();
Le standard MPI-2 présente certaines nouveautés afin de combler certains vides constatés. Il
permet entre autres, la gestion dynamique du nombre de processeurs.
Pour l’utilisation de MPI sous Windows, il est recommandé d’utiliser MPICH. Cette
installation exige d’inclure certaines choses dans PATH et de recopier certains fichiers dans le
répertoire lib du compilateur utilisé.
Chapitre 4
S’il est long et difficile d’écrire des sous-programmes génériques d’analyse numérique, qui
plus sont performants, la difficulté est décuplée s’il s’agit de le faire pour des machines à mémoire
répartie. Là aussi, on a tout intérêt à vérifier auparavant si cela n’existe pas dans une librairie ou
bibliothèque.
L’intérêt majeur de l’emploi des bibliothèques (outre le travail économisé !) vient de ce
qu’elles permettent de conjuguer portabilité et performances — au cas particulier près de la non
portabilité de certaines parties des bibliothèques des constructeurs.
Elles permettent en effet de prendre en compte une large série de facteurs, quasiment hors
d’atteinte de la part de non spécialistes :
• l’indépendance vis-à-vis du type des données (réels simple ou double précision?, réels ou
complexes ?) ;
• l’indépendance vis-à-vis de la représentation des données (matrices symétriques, bandes,
creuses ?) ;
• le choix de l’algorithme numérique le meilleur ;
• la vérification de la stabilité numérique de l’algorithme ;
• l’optimisation en fonction de l’architecture de la machine cible ;
• l’optimisation en fonction du cas à traiter (un produit de matrices se programme différemment
suivant le rapport entre le nombre de lignes et de colonnes de celles-ci).
bibliothèque ((P)ESSL chez IBM, SSL2VP(P) chez Fujitsu, ASL et MathKeisan chez NEC, SciLib
chez Cray, etc.) ;
• leur avantage est qu’elles ont (en principe !) des performances optimales pour les
machines concernées ;
• par contre elles ne sont pas portables.
Notons que plusieurs des librairies présentées dans ce chapitre ne sont accessibles, ou
majoritairement accessibles, qu’en C ou en Fortran. Il sera donc nécessaire pour certains
programmeurs de développer l’expertise qui consiste à mélanger des objets compilés en C et d’autres
compilés en Fortran.
4.2. PBLAS
4.2.1. BLAS
Démarré en 1978, BLAS (Basic Linear Algebra Subroutines) est une librairie effectuant des
opérations élémentaires d’algèbre linéaire mais d’une façon relativement optimisée. Plusieurs autres
librairies y font références. Les BLAS_LEVEL_1, BLAS_LEVEL_2 et BLAS_LEVEL_3 effectuent
respectivement des opérations de type vecteur-vecteur, vecteur-matrice et matrice-matrice.
4.2.2. PBLAS
PBLAS (Parallel BLAS) (Parallel Basic Linear Algebra Subprograms) défini et réalisé dans le
cadre du projet ScaLAPACK (lors de la seconde grande modification de cette librairie, mais qui a
donné lieu à la première version publique), en analogie avec ce qui fut fait pour LAPACK, qui faisait
un large usage du BLAS.
Fournit sur machines à mémoire distribuée tout (ou presque tout) ce que fournissent les BLAS
1, 2 et 3 sur machines à mémoire partagée.
Interface aussi proche qu’il était possible de celle du BLAS. PBLAS utilise le BLACS pour les
communications.
4.3. ScaLAPACK
4.3.1. BLACS
Ecrit en langage C, BLACS est souvent basé sur MPI, mais pas forcément (sur IBM, LAPI, qui
est la couche native de communication sur ces machines). La syntaxe d’appel au BLACS reste
identique d’un système à l’autre (d’où son intérêt).
4.3.2. LAPACK
4.3.3. ScaLAPACK
4.4. Super_LU_DIST
4.4.1. Super_LU
4.4.2. Super_LU_DIST
4.5. PETSc
Développé par le laboratoire national d’Argonne, PETSc (Portable, Extensible Toolkit for
Scientific Computation) est une librairie composée d'un ensemble de procédures permettant de
résoudre aussi bien en séquentiel qu'en parallèle des équations aux dérivées partielles et des problèmes
d'algèbre linéaire (équations linéaires et non linéaires) par des méthodes numériques itératives basées
sur les sous-espaces de Krylov. PETSc utilise MPI les communications inter-processus.
Portable sur divers environnements dont ceux de Cray, HP, IBM, NEC¸ SGI, Sun, etc. Elle peut être
utilisée directement par des programmes Fortran, C ou C++.
4.6. P_ARPACK
4.6.1. ARPACK
ARPACK (ARnoldi PACKage) est une librairie qui permet de résoudre les problèmes aux
valeurs propres par la méthode de ARnoldi. Contrairement à Lanczos, cette méthode supporte des
matrices non-sysmétriques ou encore non-hermitiennes dans le cas des complexes.
4.6.2. P_ARPACK
P_ARPACK (Parallel ARnoldi PACKage) fait la même chose que ARPACK mais en exécution
parallèle.
• Les vecteurs (Vec) : initialisation et opérations de base sur les vecteurs (addition, produit scalaire,
etc.)
• Les matrices (Mat) : initialisation et opérations de base sur les matrices creuses ou denses
(factorisations, transposition, etc.)
• Les solveurs d’équations linéaires simplifiées (SLES), surcouche des méthodes itératives
La compilation sera prétraitée pour substituer les types génériques de PETSc (Scalar,
Vec, Mat, ...) par les structures et descripteurs adaptés.
#include "mat.h"
PetscInitialize(&argc,&argv,(char *)0,PETSC_NULL);
ierr = OptionsGetInt(PETSC_NULL,"-n",&n,&flg);
/* Cr´eation d’une matrice creuse g´en´erale */
ierr = MatCreate(PETSC_COMM_WORLD,n,n,&C);
ierr = OptionsHasName(PETSC_NULL,"-column_oriented",&flg);
if (flg) {ierr = MatSetOption(C,MAT_COLUMN_ORIENTED);
for ( i=0; i<6; i++ ) v[i] = (double) i;
midx[0] = 0; midx[1] = 2; midx[2] = 3;
nidx[0] = 1; nidx[1] = 3;
ierr = MatSetValues(C,3,midx,2,nidx,v,ADD_VALUES);
ierr = MatAssemblyBegin(C,MAT_FINAL_ASSEMBLY);
ierr = MatAssemblyEnd(C,MAT_FINAL_ASSEMBLY);
ierr = MatView(C,VIEWER_STDOUT_WORLD);
ierr = MatDestroy(C);
PetscFinalize();
return 0;
}
Chapitre 5
Grid computing
5.1. Introduction
Depuis quelques années, l’Informatique répartie et les technologies associées sont dans une
constante évolution technologique et économique. Les technologies sont de plus en plus matures et
sophistiquées. Les serveurs de calcul et de stockage voient leur rapport qualité
prix en constante augmentation en intégrant les nouvelles innovations technologiques et de fabrication.
La même observation peut être appliquée aux technologies réseaux et à la bande passante
offerte qui tend à devenir illimitée d’un point de vue applicatif.
La réponse à ces changements est de passer à un modèle d’informatique répartie permettant
d’exploiter pleinement les ressources et les capacités offertes. Cet environnement offrira un service et
un accès uniformes et économiquement viables aux ressources de l’infrastructure.
Cette évolution est connue sous le nom de « Grid Computing » ou « grille de calcul ».
Le terme « The Grid » ou « grille de calcul », a été introduit pour la première fois aux Etats-Unis
durant les années 1990 pour décrire une infrastructure de calcul répartie utilisée dans des projets de
recherche scientifique et industrielle.
Dans un des premiers documents traitant des concepts de grille de calcul, les chercheurs tentent
de fournir une définition détaillée des objectifs, de la forme et de l’architecture des grilles de calcul.
Six questions fondamentales sont établies que toute personne impliquée dans la conception, le
développement ou l’utilisation des grilles de calcul est susceptible de se poser :
• Pourquoi avons-nous besoin de grilles de calcul ?
• Quels types d’applications bénéficieront des grilles de calcul ?
• Qui utilisera les grilles de calcul ?
• Comment les grilles de calcul seront utilisées ?
• Quelles sont les composantes impliquées dans la conception des grilles de calcul ?
• Quels sont les obstacles à franchir afin que les grilles de calcul deviennent
omniprésentes ?
La notion de grille de calcul s’inspire énormément de la grille d’électricité (power grid). Vers le
début des années 1900, la génération d’électricité était déjà technologiquement possible et de
nouveaux équipements utilisant la puissance électrique avaient fait leur apparition. Cependant le fait
que chaque utilisateur opère son propre générateur d’électricité était un obstacle majeur à l’adoption de
cette technologie. Ainsi la vraie révolution était la construction de la grille électrique et les réseaux de
transmission et de distribution associés. L’électricité est ainsi devenue universellement disponible.
C’est cette disponibilité universelle et économiquement viable (bon marché) qui a permis aux
industries d’adopter cette technologie et d’évoluer et de se développer par son biais.
Par analogie à cette grille d’électricité, la notion de grille de calcul (computational grid) est
définie comme étant : une infrastructure matérielle et logicielle fournissant un accès fiable
(dependable), cohérant (consistent), à taux de pénétration élevé (pervasive) et bon marché
(inexpensive) à des capacités de traitement et de calcul.
Nous parlons d’infrastructure car une grille devra fournir des ressources (calcul, stockage …) à
grande échelle. Cela nécessite une quantité significative de matériel qui constituera les ressources et
assure leur interconnexion et une quantité importante de logiciel pour bien contrôler et superviser cet
ensemble matériel.
La nécessité d’un service fiable est fondamentale. Les utilisateurs d’une telle infrastructure
s’attendent à recevoir un service prédictible, continu et performant. Sinon le développement et
l’utilisation d’applications utilisant cette infrastructure ne se produiront pas.
La cohérence suggère, tout comme dans la grille d’électricité, la présence de services standard,
accessibles via des interfaces standard et opérant selon des paramètres standard.
Un taux de pénétration élevé permet de garantir que les services seront facilement accessibles
par une large population.
Finalement l’aspect bon marché est très important d’un point de vue viabilité économique.
Dans [The Grid, Blueprint ], une grille informatique est un environnement de systèmes
d'exploitation et d'architectures hétérogènes dont l'accès est fourni à l'utilisateur sous une forme
unifiée, par un logiciel appelé intergiciel (middleware en anglais).
Les grilles sont actuellement l'objet de nombreux projets, mises en oeuvre par différentes
architectures. Il n'existe pas de définition unique unanimement reconnue.
Cependant, tout le monde est d'accord pour dire que toutes ces architectures ont un point
commun : le partage de ressources. Mais, ce n'est pas parce qu'un système assure un partage de
ressources qu'il est une grille.
Pour ce qui est des fonctionnalités de ces architectures, des modèles de programmations
qu'elles engendrent, ou même pour définir les modèles qui les valident, il n'existe pas de consensus.
Il est important de savoir quels avantages une grille de calcul est en mesure d’offrir que les
infrastructures et les technologies actuelles ne sont pas capables d’assurer. Nous exposons par la suite
quelques unes des raisons pouvant amener à déployer une grille de calcul.
Les études montrent que les ordinateurs personnels et les stations de travail sont inactifs la
plupart du temps. Le taux d’utilisation varie entre 30% (milieux académiques et industriels) et 5%
(machines grand public).
Les grilles de calcul permettront ainsi d’utiliser les cycles processeurs durant lesquels les
machines sont inactives afin de faire tourner une application nécessitant une puissance de calcul
importante et que les machines qui lui sont dédiées n’arrivent pas à assurer.
Les cycles processeur ne sont pas la seule ressource sous utilisée; souvent les capacités de
stockage les sont aussi. Ainsi il est possible qu’une grille agrège toutes ces ressources afin de les
partager entre les différents utilisateurs (on parle alors de « grille de données » ou Data Grid). Une
autre conséquence d’une telle utilisation est la possibilité de faire du partage de charge entre les
différentes ressources d’une grille.
Le fait de pouvoir fournir une importante capacité de calcul parallèle constitue une
caractéristique importante des grilles de calcul.
En plus du domaine académique, le milieu industriel bénéficiera énormément d’une telle
capacité : bioinformatique, exploration pétrolière, industrie cinématographique etc. En effet les
applications sont écrites d’une façon à pouvoir exploiter parallèlement des ressources (clusters,
machines multiprocesseur …).
Les grilles de calcul peuvent de la même manière fournir des ressources dont l’utilisation
pourra se faire en parallèle.
En partageant les ressources, une grille pourra fournir l’accès à des ressources spéciales comme
des équipements spécifiques (microscope électronique, bras robotique …) ou des logiciels dont le prix
de la licence est élevé. Ainsi ces ressources exposées à tous les utilisateurs seront mieux utilisées et
partagées et ainsi on évitera d’avoir recours à installer du nouveau matériel ou acheter de nouvelles
licences.
Du fait que les ressources fédérées par une grille de calcul soient géographiquement dispersées
et disponibles en importantes quantités permet d’assurer la continuité du service si certaines ressources
deviennent inaccessibles. Les logiciels de contrôle et de gestion de la grille seront en mesure de
soumettre la demande de calcul à d’autres ressources.
Les principales applications des grilles de calcul se feront dans les domaines suivants:
Une grille de calcul pourra agréger une importante quantité de ressources afin de fournir la
puissance de calcul nécessaire pour de nombreuses applications et que même les supercalculateurs les
plus modernes ne peuvent fournir.
Selon la nature et l’étendue de la grille, les ressources agrégées pourront aller de toutes les
stations de travail d’une université à tous les supercalculateurs des organismes de recherche d’un pays.
Comme exemples d’applications nous pouvons citer la simulation distribuée dans la
météorologie, la cosmologie et l’aéronautique…
Une grille de calcul sera utilisée pour ordonnancer en parallèle une importante quantité de
tâches indépendantes les unes des autres.
Comme exemples d’applications nous pouvons citer la recherche de clés cryptographiques, les
simulations de molécules et l’analyse du génome …
Une grille de calcul pourra fournir les ressources pour satisfaire les demandes à court terme
d’une application et que les ressources locales ne sont pas en mesure d’assurer (cycles processeur,
stockage …).
Le défi principal pour les concepteurs de telles grilles est la nature dynamique et aléatoire des
demandes faites par les utilisateurs qui peuvent constituer une large population.
Cette classe d’applications inclut les applications d’interaction entre humains dans des
environnements de simulation en temps réel (conception et interaction avec un nouveau moteur
d’avion, conception d’un plan urbain …).
Dans de telles applications, une grille de calcul pourra absorber et stocker d’importante quantité
d’informations générées.
Comme exemple d’applications nous pouvons mentionner la production d’une carte de
l’univers, la prévision météorologique à long terme, les simulations quantiques …
Une grille est une collection de ressources dont pourra bénéficier l’utilisateur. Les ressources
sont en générale réparties en plusieurs types:
1. Cycles processeur : il existe plusieurs façon d’exploiter les cycles processeurs non utilisés des
machines participant à la grille. La première est de faire tourner une application sur une machine
distante au lieu du processeur local. La seconde est d’utiliser une application conçue pour tourner ses
différentes parties en parallèle sur différents processeurs. La troisième est de faire tourner plusieurs
occurrences d’une même application sur différentes machines afin de traiter différentes données plus
rapidement.
2. Capacité de stockage : le second type de ressources disponible dans une grille est la capacité de
stockage. En effet, les machines participant à la grille pourront fournir une partie de la capacité de
stockage nécessaire au fonctionnement de la grille. Les capacités de stockage dans une grille pourront
être utilisées afin d’augmenter la capacité offerte, la performance, l’efficacité de partage et la fiabilité.
3. Equipements spéciaux et logiciels à licence élevée : certains logiciels dont les prix de licence
sont élevés seront présents en quelques exemplaires seulement dans la grille. Cette grille permettra
donc en les exposant à beaucoup d’utilisateurs, une meilleure utilisation de ces logiciels. Il en va de
même pour certains équipements spéciaux comme les microscopes électroniques et les appareils
médicaux …
Bien que chaque projet ait sa propre architecture, une architecture générale est importante pour
expliquer certains concepts fondamentaux des grilles.
machine avec un langage de description de ressources (quantité de mémoire nécessaire, temps CPU,. .
.) indépendant de la machine utilisée.
Nous expliquons ci-dessous brièvement les principales fonctions assurées par ces intergiciels :
• Ordonnancement (scheduling) : un ordonnanceur (scheduler) devra trouver la machine la plus
appropriée pour faire tourner les tâches qui lui sont soumises. Les ordonnanceurs peuvent aller du plus
simple (allocation de type round-robin) au plus compliqué (ordonnancement à base de priorités …).
Les ordonnanceurs réagissent à la charge de la grille. Ils déterminent le taux de charge de chaque
ressource afin de bien ordonnancer les prochaines tâches. Ils peuvent être organisés en hiérarchie avec
certains interagissant directement avec les ressources, et d’autres (métaordonnanceurs) interagissant
avec les ordonnanceurs intermédiaires. Ils peuvent aussi superviser le déroulement d’une tâche jusqu’à
sa terminaison, la soumettre à nouveau si elle est brusquement interrompue et la terminer
prématurément si elle se trouve dans une boucle enfin d’exécution.
• Réservation : il est possible dans les grilles de calcul de réserver les ressources à l’avance et
ceci afin de garantir une certaine qualité de service et de respecter certaines échéances. Pour cela
l’intergiciel devra fournir un système de réservation permettant aux utilisateurs d’exprimer leurs
besoins en ressources et d’effectuer la réservation.
• Services d’information et d’annuaire (Information and Directory services) : une grille est un
environnement où le nombre et la charge ressources sont dynamiques. Un objectif lors de la
conception d’une grille est de rendre les ressources facilement accessibles à tous les processus. Il est
alors nécessaire de fournir des mécanismes permettant d’enregistrer et de découvrir ces ressources et
d’identifier leur état.
• Service de nom (Naming Service): Comme dans tout système réparti, une grille devra permettre
de référencer ses ressources d’une façon standard et uniforme.
Une grille de calcul devra fonctionner avec un large spectre de technologies matérielles ou logicielles.
Tout comme les utilisateurs du WEB ne se soucient pas de savoir si les serveurs WEB tournent sur une
machine x86 ou Sparc ou bien si le système d’exploitation est Unix ou Windows, les utilisateurs d’une
grille ne veulent pas se préoccuper des détails matériels et logiciels de l’infrastructure. Un
environnement de grille idéal fournira ainsi un accès transparent aux ressources en cachant toutes les
différences « physiques ».
La quatrième couche regroupe tous les outils et les paradigmes pouvant aider les développeurs
à concevoir et écrire des applications pouvant tourner sur la grille. On y trouve plus particulièrement
des compilateurs, des librairies, des outils de conception d’applications parallèles ainsi que des
interfaces de programmation ou API (découverte et réservation de ressources, mécanismes de sécurité,
stockage …) que les développeurs d’applications pourront utiliser.
La dernière couche regroupe les applications proprement dites qui sont de nature variée :
projets scientifiques, médicaux, financiers, ingénierie …
De part leur nature, les grilles peuvent être vues suivant deux perspectives :
D’un point de vue topologique, les grilles sont de trois types selon l’ordre croissant d’étendue
géographique et de complexité : Intragrilles (Intragrids), Extragrilles (Extragrids) et
Intergrilles (Intergrids).
La plus simple des grilles est l’intragrille, composée d’un ensemble relativement simple de
ressources et de services et appartenant à une organisation unique.
Les principales caractéristiques d’une telle grille sont la présence d’un réseau d’interconnexion
performant et haut-débit, d’un domaine de sécurité unique et maîtrisé par les administrateurs de
l’organisation et d’un ensemble relativement statique et homogène de ressources. Une entreprise peut
être amenée à construire une intragrille pour augmenter la puissance de calcul de ses équipes de
recherche et développement tout en maintenant un niveau d’investissement faible en terme de
nouvelles infrastructures (comme par exemple AMD lors de la phase de conception de ses processeurs
K6 et K7 [2]).
Une intergrille consiste à agréger les grilles de multiples organisations, en une seule grille. Les
principales caractéristiques d’une telle grille sont la présence d’un réseau d’interconnexion très
hétérogène haut et bas débit (LAN / WAN), de plusieurs domaines de sécurité distincts et ayant parfois
des politiques de sécurité différentes et même contradictoires, et d’un ensemble très dynamique de
ressources.
Les intergrilles seront souvent mises en oeuvre lors de grands projets industriels (conception
d’un avion par un consortium aéronautique par exemple) ou scientifiques (modélisation de protéines)
où plusieurs organisations seront amenées à participer.
Avec la naissance de la notion de grille de calcul, la notion d’organisation virtuelle (Virtual
Organization) apparaît. Les utilisateurs pourront être groupés, selon leurs différents intérêts, dans de
telles organisations dont chacune possède sa propre politique. Chaque organisation virtuelle mettra à la
disposition de ces utilisateurs un ensemble de ressources sous la forme d’une grille.
Notons que la notion d’intergrille tend aujourd’hui à se confondre avec celle de Global
computing ou Internet computing.
Bien que les grilles offrent plusieurs avantages, seuls certains types d’applications pourront en
bénéficier. Les applications conçues afin de s’exécuter sur plusieurs processeurs en parallèle
bénéficieront le plus des grilles de calcul.
La question la plus importante est de savoir si une application pourra être écrite sous forme
d’algorithmes s’exécutant en parallèle. En effet certains algorithmes s’exécutent d’une façon
séquentielle et ne pourront pas bénéficier de la puissance de calcul parallèle des grilles.
Le travail du concepteur de l’application consistera donc à déterminer quelles sont les parties
de l’application susceptibles de tourner en parallèle et d’implémenter les algorithmes correspondants
afin de rendre l’application prête à tourner sur une grille.
Pour certaines applications difficiles à décomposer en sous applications parallèles, une grille
s’avère utile lorsque l’on souhaite exécuter plusieurs instances de l’application afin de trouver un
résultat optimal, par exemple la trajectoire idéale d’une navette spatiale.
Une autre manière de rendre les applications moins séquentielles est de les décomposer en sous
applications et d’essayer d’éliminer la dépendance entre ces sous parties en effectuant des calculs
redondants : si une partie nécessite en entrée les résultats de la partie précédente et si l’ensemble des
résultats de cette dernière est de taille raisonnable, nous pouvons faire tourner plusieurs instances de la
seconde partie en parallèle pour chacun des éléments de l’ensemble et d’écarter les mauvaises
instances lorsque le résultat de la première partie est connu. Cette forme d’exécution spéculative
(speculative execution) est bien connue des concepteurs d’architectures pour microprocesseurs. Même
lorsque l’ensemble des résultats possibles est grand ou infini, il est possible de concevoir des
heuristiques permettant de choisir un sous-ensemble fini des résultats les plus probables à traiter en
parallèle. Cela peut ne pas produire un résultat final optimal, mais selon les contraintes techniques et
économiques de l’application, les résultats seront plus ou moins acceptables.
Des décisions devront aussi être prises lors de la conception de l’application en ce qui
concerne les données à traiter. En effet en décomposant une application, nous devons prendre en
considération la nécessité d’envoyer les données à travers le réseau à une sous partie de l’application
tournant à distance. Le volume de ces données et leur temps de transfert deviennent des paramètres
importants dans la performance globale de l’application.
Lorsque le volume de ces données est important, il devient très inefficace de les envoyer à
travers le réseau. Ainsi il devient préférable de placer les données le plus proche possible du noeud
effectuant les calculs. Et lorsque les mêmes données sont utilisées par plusieurs nœuds il devient
nécessaire de répliquer ces données dans plusieurs endroits de la grille. Ainsi il faut s’arranger à ce que
chaque noeud accède aux données les plus proches. Cela illustre l’importance de la présence d’un
service permettant de localiser ces données.
Certaines données sont de nature statique, d’autres dynamiques. Dans ce dernier cas et si ces
données sont répliquées en plusieurs endroits de la grille, le concepteur devra songer à la question de la
mise à jour des différentes répliques et de la cohérence globale des données.
Pour certains types de données, il sera urgent de propager toute modification le plus rapidement
possible à toutes les répliques. Parfois cela n’est pas urgent et contribue ainsi à améliorer les
performances globales de l’application en termes de temps d’exécution. La mise à jour et l’accès
simultanés aux données posent des problèmes de cohérence de ces données. Pour cela des mécanismes
de verrouillage et de synchronisation adaptés devront être employés. Il devient alors nécessaire
d’éviter, de prévenir et de détecter tous problèmes d’interblocage et de famine bien connus dans
l’informatique répartie.
Enfin nous mentionnons qu’une même application peut avoir plusieurs instances qui
s’exécutent en parallèle dans différents endroits de la grille afin de minimiser les effets des pannes
pouvant survenir. Ces différentes instances devront se coordonner afin de ne pas modifier les données
d’une manière non cohérente et les laisser dans un état erroné.
La plupart des projets de grid actuels demeurent des projets scientifiques. Ce sont encore les
besoins en puissance de calcul ou en traitement massif de données qui restent les motivations
premières des utilisateurs. Ces arguments laissent les entreprises classiques totalement indifférentes.
Plusieurs projets de réalisation de grilles de calcul sont en cours et bénéficient du concours des
universités, des centres de recherche et des industriels.
a) Europe
a.1) Projets communautaires
Suite au travail des pionniers des Etats-Unis d'Amérique et d'Europe, des plateformes de
développement logiciel pour le calcul distribué comme Globus, Condor et Unicore sont disponibles.
L'Europe a réussi à mettre en place une grille de test fonctionnelle dans laquelle participent 20
centres européens, dans le contexte du projet EDG (European DataGrid).
Le but du projet EGEE (Enabling Grids for eScience in Europe) est d'intégrer les grilles
nationales, régionales et thématiques actuelles pour créer une grille européenne pour la recherche
européenne. Cette infrastructure sera connectée avec d'autres grilles au niveau mondial, incluant entre
autres celle de la NSF américaine (National Science Foundation US), contribuant ainsi à l'émergence
d'une grille planétaire.
Cette infrastructure se construira en réalisant deux projets pilotes, un en physique des particules
à hautes énergies et l'autre en biomédical.
Le premier projet est le Large Hadron Collider Computing Grid (LHCG), pour lequel une grille
est nécessaire pour traiter les 12 à 14 péta-octets de données annuelles résultant soit d'expériences, soit
de simulations du CERN. L'analyse requiera l'equivalent de 70000 des processeurs les plus rapides
aujourd'hui...Ses résultats intéressent quelques 6000 scientiques des universités et laboratoires du
monde entier.
L'autre projet est Biomedical Grids, pour lequel plusieurs communautés collaborent pour traiter
les données issues de la bioinformatique et du système de santé.
b) Amérique du Nord
b.1) Etats-Unis
• TeraGRID : est le résultat de plusieurs années d'effort pour déployer la plus grande architecture
mondiale distribuée pour la recherche scientifique. TeraGrid doit représenter une puissance de
calcul de 20 teraflops, avec la possibilité de gérer et stocker près d'un péta-octet de données, et
un environnement de visualisation de haute résolution.
• Access Grid : Cette grille a été développée par le Argonne National Laboratory.
• Earth System Grid II: Elle est créée dans le but de pouvoir faire des simulations climatiques
• Search for ExtraTerrestrial Intelligence (SETI@home): SETI@home est certainement
l’application en grille la plus populaire. Elle concerne la recherche intelligence extraterrestre.
Ce projet utilise des ordinateurs connectés à Internet, pour analyser des données en provenance
d’une radio télescope.
• GRID.ORG : Le Grid MP Global @ grid.org a servi entre autres au projet de Recherche pour
le Cancer sponsorisé par Intel et l'Université d'Oxford et à celui de Recherche sur l'Anthrax
sponsorisé par Intel et Microsoft
• Grid Physics Network, Particle Physics Daa Grid, NASA Information Power Grid, …
b.2) Canada
c) Asie
c.1) Japon
• Naregi : Le gouvernement japonais, des entités commerciales et des universités ont dévoilé le
projet Naregi (National Research Grid Initiative) qui a but de réaliser un superordinateur à
partir d'ordinateurs répartis sur tout le pays. Ce métaordinateur devrait atteindre une capacité de
100 Tflops en 2007.
• BioGrid : C'est un projet associant le Japon et la Chine, entre l'Université d'Osaka et l'Institut
de Microbiologie de l'Académie des Sciences de Chine.
c.2) Chine
• CNGrid (China National Grid Project): C'est un projet clé du programme National High-Tech
R&D (863 projets).
• China Education and Research Grid: Le projet a débuté en 2003 avec six universités. Une fois
terminé, la grille servira à plus de 200000 étudiants et universitaires répartis sur environ une
centaine de sites.
Australie
Nimrod : L'initiateur de ces projets est Rajkumar Buyya qui a réalisé une thèse pour obtenir un PhD
dont le titre de la dissertation est : ``Economic-based Distributed Resource Management and
Scheduling for Grid Computing'' en avril 2002.
Quelques références