Академический Документы
Профессиональный Документы
Культура Документы
Jean-Paul Sansonnet
Les Multiprocesseurs à Mémoire Partagée sont des machines parallèles de type MIMD,
composées de PEs qui sont reliés à une mémoire commune par un réseau d'interconnexion.
Les applications typiques que l'on exécute sur ces machines sont des Applications
Concurrentes. Un programme est composé de tâches (ou processus), indépendantes les unes
des autres, mais qui ont besoin de coopérer pour se communiquer des informations et qui
entrent souvent en compétition pour accéder aux ressources de la machine : PE, mémoire, E/S
etc.
2. Sommaire
Constructeurs de Base.
Les Instructions Fork&Join.
Exemple de Fork&Join.
Les Instructions ParBegin/ParEnd.
L'instruction Doall.
Les Processus.
Les Hotspots
Le Phénomène de Hotspot.
Implémentation du Fetch&Add dans le NYU.
Quelques Multiprocesseurs
C.mmp.
L'architecture Butterfly de BBN.
La Machine RP3 d'IBM.
La Gestion Mémoire dans la Machine RP3.
3. Constructeurs de Base
Les constructeurs de base pour le lancement et l'arrêt des processus sont historiquement
hérités des constructeurs de base du contrôle séquentiel de Von Neumann (a-b) : à la séquence
correspond les constructeurs permettant de délimiter un bloc d'instructions à exécuter en
parallèle ; à la conditionnelle correspond les constructeurs permettant de lancer en parallèle
deux blocs d'instructions ; à la répétition correspond les constructeurs permettant de répliquer
une action sur des données similaires ; enfin, à l'instruction d'appel correspond les
constructeurs permettant de désigner les processus.
2. Synchronisation de Processus Concurrents
Les constructeurs de base pour la synchronisation des Processus Concurrents servent à gérer
la Compétition et la Coopération entre processus.
Les instructions Fork et Join introduites par M.E. Conway en 1963 fournissent un mécanisme
de synchronisation du parallélisme permettant de contrôler l'activation de processus en
parallèle et ensuite de contrôler la poursuite du traitement après l'exécution des processus
parallèles. Ces instruction utilisent une variable de synchronisation CTR qui contient à tout
instant le nombre de processus actifs.
L'instruction Fork étiq. est une instruction de contrôle du parallélisme qui fonctionne comme
un branchement où on prend les deux branches à la fois. Si on rencontre cette instruction, on
lance le morceau de code qui commence à l'adresse spécifiée par l'étiquette de l'instruction
Fork et on continue en séquence. On obtient ainsi deux processus qui travaillent en parallèle.
Sur un ordinateur multiprocesseurs, le temps de mise en place d'un nouveau processus au
moyen d'un Fork peut être assez long : par exemple sur le système Unix-Balance qui tourne
sur Système Sequent, il faut 55 ms soit environ 25000 instructions ! Sur le système Mach, il
faut environ 3000 instructions (sur Vax). Ceci montre que ce mécanisme ne peut être utilisé
que pour lancer des tâches à très gros grain dont le temps d'exécution s'exprime en secondes.
L'instruction Join Ctr, étiq. est une instruction de contrôle du parallélisme qui exécute la
séquence indivisible suivante :
début
-- Ctr est une variable partagée de type entier
Ctr := Ctr - 1 ;
Si Ctr = 0 alors Goto étiq.
sinon exécuter l'instruction en séquence
-- C'est généralement un quit
fin
L'instruction quit a pour rôle d'arrêter un processus. Elle est généralement utilisée en
association étroite avec Join. Il existe des variantes de l'instruction Fork qui permettent de
manipuler le compteur de processus de manière insécable en même temps qu'on lance un
processus. Par exemple : Fork Ctr, étiq est équivalente à :
début
Ctr := Ctr +1 ;
Fork étiq
fin
5. Exemple de Fork&Join
Dans cet exemple, on veut lancer en tout 4 processus : P, Q, R et S puis reprendre l'exécution
dans P quand Q, R et S ont terminé. C'est Q qui lance automatiquement S quand il est appelé.
Avec les instructions Fork et Join utilisées, et en considérant que l'ordre de terminaison des
processus est celui donné ci-dessus, alors P reprend bien le contrôle de l'exécution.
Cependant, on constate que si par exemple Q avait pris du retard, on aurait l'ordre de
terminaison : R, S, P, Q et c'est Q qui aurait repris la main. Les instructions Fork et Join
obligent donc le programmeur à déterminer à l'avance la durée d'exécution des processus
lancés pour maîtriser le contrôle global du parallélisme.
6. Les Instructions ParBegin / ParEnd
Par exemple, pour effectuer les calculs de prévision météorologique (Weather Code), on
décompose l'espace en une grille à trois dimensions (a), selon l'altitude, la latitude et la
longitude. Le pas de discrétisation de l'espace produit une représentation de complexité
polynômiale (L x N x M) qui peut être stockée sous forme d'un tableau à trois indices. Dans la
version séquentielle (b), on utilise trois boucles imbriquées. Dans la version parallèle (c) une
seule instruction Doall génère tous les processus : un pour chaque point i, j, k de l'espace.
For i = 1 to L
For j= 1 to M
For k = 1 to N
u*[i,j,k] = n* pi[i,j] * u[i,j,k]
v*[i,j,k] = m* pi[i,j] * v[i,j,k]
s[i,j,k] = pi[i,j] * sigma[i,j]
End
End
End
Comme Pratt, on peut voir les processus comme le résultat de la relâche de certaines
restrictions sur les blocs de code. Ce qui différencie les subroutines du code expansé inline
c'est le fait que la macroexpansion ne peut pas être récursive. Ce qui différencie les coroutines
des subroutines c'est que les coroutines ne sont pas obligés de se terminer quand on les
appelle. Ce qui différencie les processus des coroutines c'est que les processus peuvent
s'exécuter en parallèle alors que les coroutines sont toujours concurrentes (elles partagent le
CPU unique).
Lorsqu'on crée des processus parallèles ou tâches, on peut le faire de manière statique comme
en Concurrent-Pascal ou Modula ou bien de manière dynamique comme en PL/1 ou en Ada.
Si l'allocation est statique (a), on peut réserver en mémoire partagée au début de l'exécution de
l'application. Cependant les tâches occupent leurs ressources même si elles ne travaillent pas.
Si l'allocation est dynamique (b), on peut mieux organiser l'allocation des ressources mais cela
prend du temps supplémentaire, en cours d'exécution, pour effectuer cette gestion. De plus, la
gestion dynamique des tâches est plus difficile à maîtriser.
Pour calculer les n itérations de la formule (a) on crée deux processus asynchrones : P calcule
les sommes ai + bi et Q calcule les produits résultat-de-P * ci. Les deux processus partagent
donc les résultats de Q. Le processus P se comporte comme un producteur de données et Q
comme un consommateur des données produites par P. On peut modéliser le comportement
des processus P et Q par le schéma (b).
On voit que, parce que les processus s'exécutent de manière indépendante, leurs indices
varient indépendamment selon la vitesse d'exécution propre de chaque processus. Cette
différence peut provoquer des inconvénients graves : par exemple, si P est plus rapide que Q
(cas de cette formule) alors il produit plus de résultats intermédiaires que Q ne peut en
consommer et il y a une accumulation (c) qui peut conduire à une perte d'informations. Au
contraire, si Q est plus rapide que P (+ et * sont inversés), alors il relit des résultats issus de P
qu'il a déjà traités et il produit des résultats faux (d).
Le mécanisme le plus simple de synchronisation entre des processus (Ping, Pong) qui
partagent une même variable x est l'instruction Test&Set dérivée des Operating Systems
multitâches. La variable partagée x est alors composée de deux champs (a) : un champ valeur
et un champ sémaphore d'un bit qui permet d'implémenter un certain nombre de mécanismes
de communication entre les processus Ping et Pong via la variable x. L'instruction Test&Set
réalise de manière non interruptible l'opération mémoire décrite en (b). L'instruction Test&Set
permet d'implémenter de nombreux mécanismes de contrôle d'accès. Parmi ceux-ci, les
Sémaphores de Dijkstra sont très populaires : il s'agit d'associer à la ressource dont on veut
contrôler l'accès indivisible une variable partagée de type booléen appelée sémaphore.
Code des processus Ping et Pong :
Process Ping
-- attente active sur le sémaphore :
While Test&Set (x.sem) = 0 do nothing;
F (x.value) ; -- travail sur x...
x.sem := 1
Process Pong :
-- attente active sur le sémaphore :
While Test&Set (x.sem) = 0 do nothing ;
G (x.value) ; -- travail sur x...
x.sem := 1
Lorsqu'un des deux processus veut accéder à la variable x il commence par tester si le
sémaphore est ouvert (x.sem = 1).
12.L'instruction Fetch&Add
Fetch-and-Add Généralisation Test&Set
F&A (V, e) F&ƒ (V, e) T&S (V)
Lire V ; Lire V ; Lire V ;
V := V + e V := ƒ (V , e) V := 1
Le New-York Ultra Computer (NYU) utilise un mode de synchronisation très original fondé
sur une instruction d'accès mémoire : Fetch-and-Add (V, e) où V est l'adresse d'une variable
scalaire de synchronisation située en mémoire partagée et e est une expression entière. Cette
instruction exécute de manière insécable (a) : la variable V est lue et incrémentée de la valeur
entière e. Une forme plus générale de cette instruction (b) permet de définir d'autres fonctions
(en général booléennes) que l'addition d'un entier. On a montré qu'une grande classe de
mécanismes de synchronisation pouvaient être implémentés au moyen de la simple instruction
Fetch&Add.
Exemple : on désire assigner N processus à N cases d'un tableau T pour y faire un certain
travail, puis attendre que tout le monde ait fini et continuer l'exécution. On utilise deux
variables de synchronisation : Y qui compte combien il reste de processus encore actifs et X
qui fournit aux processus lancés un bon indice pour aller chercher la case du tableau T qui les
concerne. Le programme principal (d) affecte la variable Y à N et lance en parallèle les N
processus. Chaque processus lancé (e) se met en attente active sur la variable générateur
d'indice X ; dès qu'il a une réponse (X est alors insécablement incrémenté) le processus
effectue son travail sur l'élément associé à cet indice et décrémente Y pour dire qu'il a fini.
Pendant ce temps, le programme principal se met en attente du passage de Y à 0 pour
continuer.
Dans les Supercalculateurs, la mémoire centrale est partagée logiquement : cela veut dire
qu'elle est vue par chaque processeur dans sa totalité au niveau du mécanisme d'adressage. En
pratique, elle peut être découpée en bancs mémoire ; il arrive même que les bancs soient
placés dans les processeurs ! (RP3), mais il s'agit quand même d'une mémoire commune. La
hiérarchisation dans les machines M MIMD complique encore l'organisation mémoire qui
peut être vue comme globale à un certain niveau et locale à un autre niveau (HEP).
Pour relier physiquement les bancs mémoire aux processeurs on utilise généralement un
réseau d'interconnexion. Un réseau d'interconnexion se distingue d'un réseau
d'intercommunication en cela qu'il relie des organes internes d'une machine (ici le processeur
et sa mémoire) alors que les réseaux d'intercommunication relient des machines entre elles (de
processeur à processeur). La conséquence est que les bus d'interconnexion ont généralement
un très grand débit d'information à passer et que la performance d'instructions aussi
importantes que Load et Store dépendent directement de ce débit : ils doivent avoir un temps
de service extrêmement court et une bande passante très grande. Cela peut être réalisé au
moyen d'un bus mémoire (Alliant) ou d'un bus hiérarchisé si la machine est hiérarchisée
(Cm*). Si le nombre de points à relier devient grand (NYU, RP3) on préfère alors des réseaux
multi-étages. Par contre, pour un petit nombre de processeurs (Cray) on relie tous les
processeurs à tous les bancs (Réseau Crossbar).
Pour éviter la contention mémoire dans le cas d'une mémoire commune (avec ou sans
découpage en bancs) on adjoint très souvent une mémoire cache à chaque processeur. Cette
mémoire locale rapide a deux avantages : elle permet d'accélérer les accès par effet-cache et
elle évite aux processeurs d'accéder à la mémoire commune, diminuant ainsi la contention.
Cependant, on sait que la gestion d'un cache est assez complexe dans une machine
séquentielle classique. Dans un environnement multiprocesseurs, cette gestion est encore plus
difficile et différentes politiques ont été proposées.
Dans une machine monoprocesseur classique la mémoire centrale idéale est une mémoire de
taille infinie et de temps d'attente nul pour le processeur. Dans un Multiprocesseur à Mémoire
Partagée, la mémoire idéale doit posséder une propriété supplémentaire : elle doit être
multiports. Une mémoire multiports (a) est capable de servir les N PEs de manière
indépendante, c'est-à-dire est capable d'effectuer N requêtes émanant de N PES
simultanément dès lors que les requêtes portent sur des mots mémoire d'adresse différente (en
effet, en cas d'accès à une variable partagée, il est indispensable de sérialiser les demandes)
A l'heure actuelle, on sait à peine réaliser des mémoires bi-ports. La plupart du temps, on a
affaire à une mémoire monoport classique (b) où les demandes sont systématiquement
sérialisées par un arbitre : ceci crée un goulet de contention très grave.
Pour éviter ce goulet, on a recours à deux techniques complémentaires que l'on peut combiner
:
Les Caches (c) : on place entre le PE et la mémoire partagée une petite mémoire
locale très rapide qui contient des blocs de mémoire centrale. L'accès au cache se fait
de manière classique en terme de mots. Si lors d'un accès au cache le mot demandé par
le PE n'y est pas, alors on va chercher le bloc qui contient ce mot en mémoire
centrale : l'accès à la mémoire centrale se fait donc en terme de blocs et ne se produit
qu'en cas de défaut dans un des caches ; il est beaucoup moins fréquent que l'accès au
cache ce qui diminue le phénomène de contention dû au fait que la mémoire centrale
n'a qu'un seul port. De plus les caches permettent d'avoir des temps d'attente nuls pour
le PE.
Les Bancs (d) : on découpe la mémoire centrale en bancs mémoire dont le nombre est
généralement (mais pas forcément) égal au nombre de PEs. Dans ce cas, les PEs et les
bancs mémoire doivent être reliés par un réseau d'interconnexion (généralement de
type dynamique multi-étages). Si les N PEs envoient N requêtes dont les adresses sont
situées dans des bancs mémoire différents, alors tout se passe comme si on avait une
mémoire N-ports ; cependant pour qu'à chaque ensemble de requêtes les adresses
soient bien réparties sur les bancs mémoire, il faut que l'organisation des données dans
l'application se prête à une telle distribution !
15.Principe de Fonctionnement d'un Cache
Dans une machine séquentielle classique, le cache n'est utilisé que pour accélérer les accès
mémoire. Le processeur fournit au contrôleur de cache l'adresse de la variable qu'il souhaite
accéder en lecture et celui-ci par diverses techniques (dispositifs associatifs, fonctions câblées
de Hash coding ...) peut dire si la donnée est dans le cache (cache hit) ou non (défaut de
cache) ; si elle n'y est pas, il la lit en mémoire centrale et la place dans le cache en vue d'un
prochain accès.
On possède donc deux copies de la valeur de la même variable. Pour maintenir la cohérence
entre ces deux copies, on utilise deux techniques :
Dans un Multiprocesseur à Mémoire Partagée qui utilise un cache associé à chacun de ses PEs
(a), si une variable est partagée par plusieurs processus, il arrive alors qu'on ait plusieurs
copies dans plusieurs caches de la valeur de la variable qui est en mémoire partagée. Si une
des copies est modifiée par un PE, le Write-Through n'est plus suffisant pour maintenir la
cohérence. Il faut aussi effectuer des mises-à-jour dans tous les autres caches où il existe une
copie de cette variable.
On distingue trois grandes techniques pour résoudre le problème de la cohérence des données
dans les caches :
1. Mécanismes Matériels :
1. Algorithmes Logiciels : les variables partagées sont détectées par le compilateur qui
les marque pour qu'elles ne puissent pas être montées dans les caches.
2. Cache partagé (b) : le cache est décomposé en bancs et le réseau d'interconnexion est
placé entre les bancs du cache et les processeurs. La mémoire commune apparaît alors
(à un degré de vitesse près) comme une mémoire secondaire.
17.Multiple Write Back
Lorsqu'un PE lit ou écrit dans son cache, tous les espions de bus associés aux autres caches
consultent le répertoire de leur cache pour faire passer le bloc contenant le mot accédé (s'il y
est) dans un nouvel état selon le diagramme (c).
Chaque répertoire de cache est accédé par le PE et l'espion de bus. Plusieurs variantes de cet
algorithme ont été proposées : Synapse, SPUR de Berkeley, Illinois...
Flèches en gras : Transitions dues aux PEs
Flèches en maigre : Transitions dues aux espions de bus.
Réservé : Le bloc a été modifié par une seule écriture reportée sur le bus ;
Modifié : Le bloc a été modifié par plus d'une écriture : c'est la seule copie à jour.
Etat Modifié : le cache qui possède la bonne copie la fournit et met à jour la mémoire.
Ces deux blocs deviennent alors Valides.
Autres états : La mémoire fournit le bloc.
Etat Modifié : le cache qui possède la bonne copie la fournit et met à jour la mémoire.
Le bloc écrit passe à l'état Réservé et les autres blocs dans les caches à l'état Invalide.
Autres états : La mémoire fournit le bloc. Le bloc écrit passe à l'état Réservé et les
autres blocs dans les caches à l'état Invalide.
18.Multiple Write Through
Dans les algorithmes basés sur le principe du Write Through (a), en lecture, on autorise la
copie multiple dans les caches de blocs issus de la mémoire centrale partagée ; en écriture, il
faut effectuer autant d'écritures qu'il y a de copies, y compris en mémoire (b)
La station de travail Firefly de DEC utilise un algorithme de maintien de la cohérence des
caches basé sur des espions de bus, de type Write Through. Dans cette architecture, on
dispose d'un bus spécial pour effectuer les mises à jour. Le diagramme des transitions d'état
est donné en (c) ; les états notés miss-P et hit-P dans le diagramme (c) indiquent que la ligne
Bloc-Partagé est active.
Archibald et Baer à qui on doit une classification des méthodes de gestion des caches et les
diagrammes de transition d'états ont effectué des comparaisons entre les méthodes Write
Through et Write Back :
si on a peu de données partagées, les algorithmes sont très proches les uns des autres,
si on a beaucoup de données partagées, alors la mise à jour immédiate (Write
Through) donne de meilleurs résultats que l'invalidation (Write Back) surtout si le le
nombre de blocs partagés est faible ; si le taux de partage diminue ou si le nombre de
partage augmente alors l'écart de performances entre les deux techniques diminue.
Valide-exclusif : Le bloc est non modifié ; il n'existe pas de copies dans un autre cache.
Dans les Multiprocesseurs à Mémoire Centrale Partagée dont le réseau d'interconnexion n'est
pas un Bus, il n'est pas possible d'utiliser des algorithmes de type espion de bus car il n'est
plus possible de façon simple et efficace de connaître en permanence la situation exacte des
blocs au niveau de chaque cache. On utilise alors un Répertoire Centralisé qui contient l'état
de chaque bloc et sur lequel on peut appliquer les techniques Write Through ou Write Back.
L'algorithme le plus ancien de Répertoire Centralisé est celui que Tang a proposé en 1976.
Son principe est que les blocs sont diffusés en lecture mais qu'un seul bloc peut être modifié
en écriture. Son fonctionnement est le suivant :
En 1978, Censier et Feautrier ont proposé un algorithme semblable à celui de Tang, mais dont
l'organisation du Répertoire Central est différente (a). On associe à chaque bloc un vecteur de
présence qui contient un bit par PE et un bit supplémentaire de Lecture/Ecriture :
Les résultats en simulation on fait apparaître des résultats très contrastés selon les types de
programmes parallèles utilisés (91% de présence dans un cas, 3% dans un autre !). Cependant
l'algorithme à numéro de version est celui qui présente le moins de contrastes d'un programme
à l'autre : c'est lui qui exploite le mieux la localité de l'information.
Depuis longtemps on a cherché à réaliser des mémoires Multiports pour les Multiprocesseurs
à Mémoire Partagée car les mémoires Multiports ne nécessitent pas de réseau
d'intercommunication. Une mémoire Multiports (a) est une mémoire centrale qui peut servir
simultanément plusieurs demandes (lectures et/ou écritures) à la seule condition que les mots
accédés par les demandeurs soient d'adresses différentes.
Ainsi, les seules attentes dues à la sérialisation des demandes ne se produisent que dans le cas
d'accès à une variable partagée.
Une première façon de réaliser une mémoire Multiports est de la découper en bancs.
Chaque banc fournit un port qui permet de lire et d'écrire indépendamment à condition
que les mots accédés par les demandeurs soient sur des bancs différents : ceci est une
contrainte très importante dans l'organisation des accès mémoire.
Des réalisations de mémoire bi-ports existent, en particulier dans les Multiprocesseurs
à bus. Dans l'exemple (b) on a un système à deux bus et deux PEs qui se partagent une
mémoire RAM à double accès. Il ne s'agit pas vraiment d'une mémoire bi-port car un
arbitre est placé sur la carte mémoire qui sérialise les accès ; en pratique, on est quand
même très près des performances d'une telle mémoire.
On peut construire une vraie mémoire bi-port en dupliquant la mémoire (c) : en mode
lecture, on peut lire sur chaque port (associé à chaque banc) en un seul cycle. En
écriture, il faut deux cycles : lors du premier cycle on écrit dans le banc associé au port
et lors du deuxième cycle on écrit dans l'autre banc (on croise les écritures). Cette
méthode ne peut être généralisée à N ports, même si on dispose de N copies de la
mémoire !
22.Mémoire Multiports de Tanaka
Une Architecture de mémoire multiports complète a été proposée par Y. Tanaka en 1988. Elle
utilise la mémorisation des informations sous forme de parité. Considérons le bit i du mot j de
la mémoire :
Pour lire : on calcule sa parité sur les bancs composant une ligne. Dans l'exemple ci-
dessus, si on lit le le bit i du mot j on trouve 1 comme parité : le bit i du mot j vaut
donc 1. Chaque ligne de bancs correspond à un port de lecture.
Pour écrire : on lit le banc associé au port d'écriture et on inverse le bit s'il est différent
du bit à écrire (cette opération est effectuée simultanément sur tous les bancs d'une
même colonne) ; le fait d'inverser un seul des bits i sur une ligne change la parité de la
ligne de bits i, donc le résultat de la prochaine lecture du bit i. Dans l'exemple ci-
dessus, si le port 2 veut écrire un 0 dans le bit i du mot j, il faut lire d'abord la parité et
comme elle est différente de 0 il écrit un 1 dans tous les bit i du mot j des bancs de la
colonne 2. Ainsi à la prochaine lecture du bit i du mot j sa parité vaudra 0.
L'inconvénient de cette architecture est qu'au lieu d'avoir un seul banc mémoire on doit en
utiliser N2 pour réaliser une mémoire à N ports de lecture et N ports d'écriture ; de plus, au
temps d'accès mémoire s'ajoute un temps en log N pour le calcul de la parité.
La machine est organisée autour d'un réseau d'interconnexion de type point à point qui relie
les drivers des bancs mémoire aux caches des modules processeurs. On utilise des liens série à
très haut débit réalisés en technologie AsGa, seule compatible avec les débits nécessités par
les demandes émanant des caches. Par exemple, pour une machine M3S ayant 64 PEs et 64
bancs mémoire et avec un débit sur les liaisons série de 1 Giga bit/s, la bande passante du
réseau est de 8000 Mega Octets/s (contre quelques centaines de Mega Octets/s pour les
systèmes actuels à base de bus).
Un dispositif de cohérence des données dans les caches de type répertoire est utilisé. Le
répertoire est centralisé au niveau de la mémoire mais distribué sur les modules : chaque
module mémoire ne gère que les problèmes de cohérence des blocs qui le concernent. Les
messages sont émis via une liaison série de cohérence qui autorise la diffusion des messages.
La gestion distribuée de la cohérence des caches n'est pas génératrice de goulet d'étranglement
et préserve la structure modulaire de M3S.
24.Le Phénomène des Hotspots
Si dans une application, une variable partagée a un taux d'utilisation de 1% des accès alors la
courbe de réponse globale du réseau se dégrade. Si cette variable a un taux d'utilisation de 4%
des accès mémoire, alors la courbe de réponse globale du réseau devient vite inopérante !
Encore plus grave, si on utilise un réseau de type Multi-étages bloquant (par exemple un
Oméga) alors la contention qui se produit sur le port mémoire bloque aussi les ports amont du
switch associé à ce port ; à son tour ces points chauds bloquent les switches en amont ... ainsi,
peu à peu une grande partie du réseau est bloquée, ce qui altère des requêtes qui n'ont pourtant
rien à voir avec le point chaud initial : c'est le phénomène de saturation en arbre. Dans le
NYU, la gestion factorisée des Fetch&Add au niveau des switches permet de diminuer les
accès effectifs aux données de synchronisation et de limiter très notablement l'effet de
hotspot.
Le New York Ultracomputer (NYU) a été développé par A. Gottlieb au Courant Institute de
New York University. C'est un Multiprocesseur MIMD possédant jusqu'à 4096 bancs
mémoire et jusqu'à 4096 processeurs organisés autour d'un réseau d'interconnexion de type
Multi-étages Oméga (a).
Les bancs mémoire possèdent chacun un contrôleur (b) qui outre l'arbitrage des accès au banc
réalise l'instruction Fetch&Add (V, e). Dans ce Multiprocesseur, l'instruction Fetch&Add est
aussi implémentée au niveau des switches du réseau d'interconnexion. Les switches sont
intelligents : ils peuvent concentrer les Fetch&Add portant sur une même variable de
synchronisation. Par exemple (c), si un switch du réseau reçoit en même temps un
Fetch&Add(V, e0) et un Fetch&Add(V, e2) il mémorise e0 dans une petite mémoire locale et
fait suivre un Fetch&Add(V, e0 + e2) qui concentre les deux demandes. Dès qu'il reçoit la
réponse old-V, il éclate correctement les deux réponses : old-V et old-V+e0 vers ses deux
demandeurs. Cette concentration des demandes pour les variables de synchronisation diminue
énormément les conflits mémoire.
26.La Gestion des Processus dans les Supercalculateurs
La classe des Supercalculateurs est définie par les Multiprocesseurs MIMD et les machines
Vectorielles. Ces deux types de machines ont une mémoire centrale commune. Dans les
Supercalculateurs, il arrive très souvent que le modèle de base (MIMD ou SIMD) soit enrichi
d'un niveau hiérarchique supplémentaire pour augmenter le parallélisme ; généralement ce
niveau supérieur est de type MIMD : cela donne lieu à des machines M MIMD ou bien M
SIMD dont l'architecture est très complexe.
Pour qu'une machine exécute des tâches en parallèle, il faut d'abord extraire ces tâches
pour les porter à la connaissance de la machine : c'est le Partitionnement. Cela peut se
faire de manière manuelle (c'est l'utilisateur qui écrit dans le code de l'application où
commencent et où finissent les tâches) ou bien de manière automatique grâce à un
outil logiciel (compilateurs spéciaux ...) ou directement dans le matériel (modèles
Data/Demand driven...). Dans les Supercalculateurs de type MIMD on laisse
généralement le travail de Partitionnement au programmeur ; dans la plupart des cas le
langage de description des tâches est généralement un Fortran étendu à des
déclarations de tâches. Dans les Supercalculateurs de type SIMD le langage de
description des tâches est généralement un Fortran qui possède des outils spéciaux de
vectorisation, capables d'extraire automatiquement le parallélisme pipe-line.
Tous le Supercalculateurs ayant au moins un étage MIMD ont des problèmes de
synchronisation. Ces problèmes se produisent quand plusieurs processus ont besoin de
travailler sur une même variable en mémoire commune. Plusieurs types de
mécanismes ont été introduits pour gérer la synchronisation. Ces mécanismes sont
hérités des mécanismes déjà définis pour les Operating Systems multitâches
fonctionnant sur des machines séquentielles classiques.
Quand on a partitionné une application en N processus, on dispose généralement de M
processeurs pour l'exécuter, tel que M < N. Cela pose alors le problème du scheduling,
c'est-à-dire de l'affectation temporaire de processus à des processeurs : à tout moment,
il faut qu'il y ait un maximum de processeurs au travail, mais en même temps, il ne
peut y avoir qu'un seul processus actif par processeur. De multiples algorithmes
d'allocation ont été étudiés pour les Supercalculateurs. Certains sont statiques : c'est le
compilateur qui effectue l'allocation Processus/Processeur une fois pour toute ;
d'autres sont dynamiques : on mesure la charge des processeurs au moment de
l'exécution pour décider de l'allocation Processus/Processeur.
27.Le Partitionnement dans HEP
Le Heterogeneous Element Processor (HEP) de la firme Denelcor Inc. a été construit en 1982
mais n'a pas été commercialisé. Cette machine parallèle possède cependant des
caractéristiques intéressantes au niveau du traitement du parallélisme. Il s'agit d'une machine
de la classe MIMD hiérarchisée : les applications (ou Jobs) sont décomposées par le
programmeur en tâches qui sont elles-mêmes décomposées par le programmeur en processus.
Les tâches et les processus sont exécutés en parallèle. Les instructions des processus sont
exécutées en mode séquentiel. Le grain de parallélisme est donc assez fort. Une tâche est
affectée dans son entier à un processeur. Un processeur peut supporter plusieurs tâches, mais
il est limité à un nombre maximal de 50 processus. Avec ses 16 processeurs, HEP peut donc
supporter des applications parallèles ayant jusqu'à 800 processus.
2.Scheduling tournant :
La deuxième originalité de la machine HEP est que pour réduire les problèmes de blocage dûs
à la contention sur les accès à la mémoire de données, on déschédule systématiquement le
processus pendant son accès à la mémoire de données. Pour cela, on dispose de plusieurs files
d'attentes : file des accès à la mémoire, file des tâches et pour chaque tâche sa file des
processus. En faisant "tourner" les processus dans ces files, on arrive à alimenter un certain
nombre d'unités fonctionnelles qui travaillent en mode Pipe-line.
Dans la machine HEP, on dispose d'un mécanisme d'équilibrage dynamique de la charge qui
utilise des variables de synchronisation. Pour les variables déclarées "variables de
synchronisation" (elles sont annotées par un $ dans le programme ; exemple : $toto) est
associé un bit noté F/E qui indique si le mot est plein (F/E =1) ou vide (F/E =0). Les lectures
et les écritures de ces variables testent au préalable le bit F/E : en cas de lecture, il faut que le
bit F/E soit égal à 1 pour que la lecture réussisse, sinon le processus qui lit est bloqué ; en cas
d'écriture, il faut que le bit F/E soit égal à 0 pour que l'écriture réussisse, sinon le processus
qui écrit est bloqué. On dispose aussi de Get ($toto) qui consulte la valeur de la variable de
synchronisation $toto de manière non destructive (F/E non modifié) et non bloquante
(indépendamment de l'état du bit F/E de $toto).
Exemple :
Les tableaux a, b, c qui ont 100 éléments sont des variables globales. On veut générer 10
processus qui vont se partager le travail des 100 itérations. Pour cela, on crée 10 processus
identiques qui se synchronisent sur une variable $n qui génère les indices : l'instruction i:= $n
n'est exécutée que si le F/E de $n est = 1 ; alors le F/E <--0 ce qui bloque tout accès à $n par
les autres processus ; l'instruction $n := i+1 incrémente le générateur $n et son F/E passe en
même temps à 1 ce qui autorise les autres processus à accéder au nouveau $n.
Process Pi
Local(i); Global(a, b, c); Synchro($n) ;
Begin
i := $n; -- ssi = Full
$n := i + 1; -- ssi = Empty
If i < 100 Then a(i) := b(i) + c(i)
...
End
29.Architecture de HEP
Le Multiprocesseur HEP a une mémoire partagée organisée en 128 bancs. Il comporte 16
processeurs pipe-line qui sont interconnectés aux bancs mémoire via un réseau multi-étages.
Dans la machine HEP, les processus sont déschédulés lorsqu'ils effectuent des accès mémoire.
La commutation de contexte est extrêmement rapide. Ceci permet d'alimenter les Unités
Fonctionnelles Pipe-line flottantes avec des instructions provenant de plusieurs processus.
Ainsi les instructions sont indépendantes les unes des autres et ne limitent pas le pipe-line.
Pour s'y retrouver, chaque instruction possède un champ TAG qui contient le numéro de son
processus : il permet d'aiguiller les résultats correctement.
30.C.mmp
Le temps de cycle mémoire d'un PDP-11/40 est de 2,5 ms. Le temps de traversée du Crossbar
(translation d'adresse, traversée des switches et des câbles) est de 1 ms seulement ce qui est
largement compatible avec le cycle du PDP-11.
Avec le Crossbar on peut effectuer 16 accès mémoire simultanément à condition que les
adresses soient différentes. Evidemment, le nombre de PEs est faible et cette architecture n'est
pas généralisable en raison de la montée quadratique du nombre de switches nécessaires à la
construction des Crossbars.
(soit 16 fois plus rapide). Ceci est surtout dû à l'utilisation du Crossbar qui n'introduit pas de
contention et qui est extrêmement rapide à traverser.
Le système Hydra permet de détecter les pannes sur les PEs grâce à un bus général qui sert
aux interruptions externes et qui a permis aussi d'implémenter un mécanisme de Watchdog :
chaque PE doit positionner un bit au moins toutes les quatre secondes sinon il est suspecté de
mal fonctionner ; cependant cette technique ne permet pas de détecter les pannes transitoires.
Les essais sur le C.mmp et sur Hydra ont montré que la machine était utilisable mais on lui a
fait plusieurs reproches quant à la facilité d'utilisation :
Les PDP-11 n'offrent qu'un très petit espace d'adressage (64 ko) et la gestion de
l'espace en mémoire partagée est difficile.
Le plus grave problème a été le debug et la trace des processus en cours d'exécution.
Le manque d'outils s'est fait cruellement sentir
Le projet C.mmp a été suivi par le projet Cm*, un Multiprocesseur à bus hiérarchisés, décrit
dans le chapitre sur les réseaux d'interconnexion.
L'originalité de l'Architecture Butterfly de BBN est qu'elle peut être vue soit comme une
machine à mémoire distribuée soit comme une machine à mémoire partagée :
En tant que machine à mémoire distribuée, elle comprend jusqu'a 256 PEs qui sont
composés d'un processeur Motorola 68 000 (ou 68 020 avec coprocesseur 68 881),
d'une mémoire locale (de 1 à 4 Mo) et d'un Processeur Contrôleur de Noeud (PCN)
qui utilise le réseau dynamique de type Oméga (appelé Butterfly par BBN) pour
effectuer la messagerie.
En tant que machine à mémoire partagée, elle est composée de 256 PEs et de 256
Bancs mémoire reliés par un réseau d'interconnexion dynamique de type Oméga
construit à base de switches 4 ¥ 4.
Le PCN utilise un MMU (Memory Management Unit) pour traduire l'adresse virtuelle du
68000 en adresse réelle, de manière à ce que les bancs représentent un espace global contigu.
Le temps d'accès mémoire en mode local est de 2 ms et en mode global via le réseau Oméga il
est de 6 ms (en l'absence de contention) ce qui est très rapide. Le réseau Oméga de la
Butterfly de BBN n'est pas combinant comme celui du NYU ou du RP3 et si plusieurs
demandes entrent en collision sur un switch, toutes sont "tuées" sauf une.
Le réseau est composé de 4 étages maximum de switches 4 x 4 réalisés sous forme d'un VLSI
spécifique. Avec 8 de ces VLSI sur une carte on peut réaliser une machine 16 PEs x 16 BMs
(a). Le débit maximal sur un lien du réseau est de 32 Mbit/s. Une configuration à 256 PEs
travaillant sur un produit de matrices a produit 115 MIPS soit un gain de 230 (efficacité de
91%) par rapport à un seul PE (0,5 MIPS).
Le Research Parallel Processor Project (RP3) de G.S. Almasi a été mis en place au centre de
recherches Watson d'IBM, en collaboration avec le projet NYU, pour étudier le parallélisme
massif. L'architecture de RP3 est donc très proche de celle de NYU : il s'agit d'un
Multiprocesseur de type M MIMD organisé physiquement en 8 Armoires (a) contenant 64
PEs et 16 Processeurs d'E/S (b). Les PEs du RP3 (c) disposent chacun d'un cache de données
géré par un MMU. Le CPU est réalisé à base du Microprocesseur Risc d'IBM, le ROMP qui
équipe les PC-RT. Il est associé à un coprocesseur flottant via un bus interne.
La puissance théorique maximum du RP3 est de 1300 MIPS et de 800 Mflops. La principale
originalité du RP3 réside dans son réseau d'interconnexion qui est double :
Le premier réseau est réalisé dans une technologie extrêmement rapide : en ECL (avec
des boîtiers TMC de refroidissement comme dans l'IBM 3090). Ce réseau est utilisé
uniquement pour faire des lectures et des écritures en mémoire. C'est un réseau de type
Multi-étages Oméga organisé en 4 étages de switches 4 x 4 pour une configuration de
512 PEs. Son horloge de base a un Tc = 20 ns ; un message prend deux cycles pour
traverser un switch ; le temps de traversée du réseau est donc de 2 x 20 ns x (étages +
remplissage du pipe-line message) ; par exemple, pour un message de 8 octets, il ne
faut que 320 ns pour traverser ce réseau ! C'est 6 fois plus rapide que dans le réseau
Oméga du Butterfly de BBN.
Le deuxième réseau est réalisé en technologie Cmos, beaucoup moins rapide que
l'ECL, mais il effectue les opérations de combinaisons des requêtes multiples et le
Fetch&Add comme dans le NYU. Les concepteurs auraient préféré un seul réseau
combinant en ECL, mais les contraintes technologiques ne le permettaient pas en
1987. Ce réseau est aussi de type Multi-étages Oméga à 64 ports. Il est composé de 6
étages de switches 2 x 2 ; un étage supplémentaire multiplexe les ports vers les 512
PEs. Son chemin de données est plus large que celui du premier réseau ce qui
compense la lenteur de la technologie mais en cas de contention les opérations de
combinaison des requêtes ralentissent quand même sérieusement le temps de
traversée.
33.Gestion Mémoire dans la Machine RP3
La gestion mémoire du RP3 a été conçue pour offrir une très grande flexibilité d'approche
entre les architectures à mémoire partagée et les architectures à mémoire distribuée. On peut
choisir entre un mode d'adressage purement local et un mode d'adressage où l'espace
d'adressage est distribué sur les PES. Le partitionnement de la mémoire entre zones locales et
globales est dynamique : il est effectué à l'exécution par un mécanisme sophistiqué de
transformation d'adresses qui permet de faire varier graduellement la part de mémoire locale
et la part de mémoire globale. Ainsi, le RP3 peut fonctionner en Multiprocesseur à Mémoire
Partagée ou bien en Machine à Passage de Messages, mais aussi en mode mixte.
Le RP3 possède 4 Giga octets de mémoire physique (32 bits d'adresse physique) répartis en
512 modules de 2 Mega Octets. Son espace d'adressage logique est donc égal à son espace
d'adressage physique. La transformation d'adresse logique en adresse physique absolue ne
porte que sur le caractère local/global des pages. Le RP3 offre en plus un mécanisme
d'entrelaçage matériel des adresses sur les PEs qui peut varier entre 0 (pas d'entrelaçage ) et 9
bits (pour une configuration maximale de 512 PEs). Le degré d'entrelaçage étant fixé pour
chaque page, les mots consécutifs sont répartis automatiquement sur les PEs pour que les
accès parallèles à cette page utilisent bien la décomposition en modules.
Le RP3 utilise des caches dont la cohérence est gérée en logiciel avec une assistance
matérielle. Les données cacheables pendant un intervalle de temps caractéristique sont
marquées par le compilateur. Les mécanismes matériels permettent de gérer les marques et les
franchissement de point de synchronisation.
Les applications qui ont été testées sur le RP3 sont essentiellement de type SPMD (Single
Program Multiple Data). Il s'agit d'un mode de fonctionnement intermédiaire entre le mode
MIMD et le mode SIMD où on exécute le même code de processus sur des PEs différents,
avec des données différentes et de manière asynchrone.
Retour