Fondements de l’architecture parallèle
Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar
Ce que la séance construit
Les processeurs ne gagnent plus en fréquence depuis le milieu des années 2000 : ils gagnent des cœurs. Un programme ne va donc plus plus vite tout seul ; il doit répartir son travail. La séance pose les fondements du cours : pourquoi le parallélisme s’est imposé, comment classer les machines, comment la mémoire est organisée, comment mesurer le gain et ce qui le limite.
D’où l’on vient, où l’on va
Amont. Architecture des ordinateurs (processeur, registres, mémoire, cache) ; programmation Python (fonctions, boucles).
Cette séance. Pourquoi le parallélisme, taxonomie de Flynn, hiérarchie mémoire et caches, accélération et efficacité, lois d’Amdahl et de Gustafson, granularité et surcoût.
Aval. Séance 2 : cohérence des caches de plusieurs cœurs (MSI, MESI). Labs associés : Lab 0 (prise en main), Lab 1 (penser parallèle), Lab 2 (plusieurs cœurs).
Processeur, fréquence, cycle
Le processeur exécute les instructions. Sa fréquence d’horloge (en hertz) donne le nombre de cycles par seconde : un processeur à \(3\) GHz bat \(3\) milliards de cycles par seconde.
Un cycle d’horloge est un « battement » de l’horloge, l’unité de temps qui rythme toutes les opérations ; une instruction prend un ou plusieurs cycles selon sa complexité. À \(3\) GHz, un cycle dure \(1/3\) ns, soit environ \(0{,}33\) ns.
Pendant des décennies, le nombre de transistors par puce a doublé environ tous les deux ans (loi de Moore), et les performances ont suivi presque au même rythme. Le gain venait de trois sources :
Un programme écrit une fois allait plus vite à chaque génération, sans être modifié. Ce n’est plus le cas depuis le milieu des années 2000.
Lecture. Les transistors (gris) doublent environ tous les deux ans : loi de Moore. La fréquence (bleu) plafonne à quelques GHz à partir du milieu des années 2000 (Pentium 4 à \(3{,}8\) GHz, novembre 2004). Les cœurs (rouge), un seul jusque-là, se multiplient ensuite.
Puissance dissipée
\[ P = C \times V^2 \times f \]
\(C\) : capacité commutée ; \(V\) : tension ; \(f\) : fréquence.
Vers 2004, une limite physique est atteinte :
La solution : plusieurs cœurs qui travaillent ensemble, au lieu d’un processeur très rapide.
Lecture. Si \(V\) suit \(f\) (hypothèse d’école), \(P\) varie comme \(f^3\) : à \(0{,}8\,f\), un cœur dissipe \(0{,}512\) fois la puissance ; deux cœurs à \(0{,}8\,f\) font \(1{,}6\) fois le travail pour \(1{,}024\) fois la puissance.
Lecture. Un seul processeur enchaîne les huit tâches : le travail se termine à \(8t\). Quatre processeurs identiques en prennent deux chacun et finissent ensemble à \(2t\) : accélération \(8t/2t=4\). Le gain suppose des tâches indépendantes ; une tâche qui attend le résultat d’une autre ne peut pas démarrer en même temps qu’elle.
Énoncé
Un processeur mono-cœur de 2004 fonctionne à \(3{,}6\) GHz. Un processeur moderne a \(8\) cœurs à \(2{,}4\) GHz chacun.
Question : quel est le gain théorique maximal de performance ?
Correction
Hypothèse : même nombre d’instructions par cycle dans les deux processeurs.
Note importante : ce gain n’est atteint que si le programme occupe parfaitement les \(8\) cœurs. En pratique, la partie séquentielle du code l’empêche (loi d’Amdahl). Un programme séquentiel, lui, va \(2{,}4/3{,}6 = 0{,}67\) fois aussi vite : il ralentit.
Lecture. Michael Flynn classe les architectures selon le nombre de flux d’instructions et de flux de données traités simultanément ({Single} ou {Multiple}). SIMD : un contrôle commande plusieurs unités de calcul ; MISD : plusieurs traitements sur une même donnée.
SISD
({Single Instruction, Single Data}) Architecture séquentielle classique : une seule instruction est exécutée sur une seule donnée à la fois. C’est le modèle de von Neumann traditionnel.
Exemples : Intel 8086, ARM Cortex-M0, microcontrôleur d’une carte Arduino Uno. Usage : microcontrôleurs, systèmes embarqués.
SIMD
({Single Instruction, Multiple Data}) Une même instruction s’applique simultanément à plusieurs données (traitement vectoriel, tableaux).
Exemples : SSE/AVX (Intel), NEON (ARM), GPU (SIMT chez NVIDIA, une variante de SIMD). Usage : traitement d’images, calcul scientifique, apprentissage profond.
Lecture. Sur le schéma, quatre additions scalaires demandent quatre instructions ; une seule instruction vectorielle additionne les quatre paires d’un coup.
MISD
({Multiple Instruction, Single Data}) Plusieurs instructions différentes traitent la même donnée. Très rare en pratique, surtout utilisé pour la redondance.
MIMD
({Multiple Instruction, Multiple Data}) Plusieurs processeurs exécutent des instructions différentes sur des données différentes. C’est l’architecture la plus flexible et la plus courante aujourd’hui.
Exemples : processeurs multicœurs, grappes ({clusters}). Usage : serveurs, stations de travail, supercalculateurs.
Les classes se cumulent
Un multicœur est MIMD ; chacun de ses cœurs exécute aussi des instructions SIMD (AVX).
Énoncé
Classer les architectures suivantes selon Flynn :
Correction
Un cycle dure environ \(0{,}3\) ns à \(3\) GHz ; un accès à la mémoire principale prend \(60\) à \(100\) ns, environ \(200\) à \(300\) fois plus (\(180\) à \(300\) cycles). La hiérarchie mémoire range le stockage en niveaux ; chaque niveau sert de cache au niveau inférieur.
Lecture. Ordres de grandeur des processeurs de bureau des années 2020 (\(1\) ns \(=3\) cycles à \(3\) GHz). Exemple daté : AMD Zen 4 (2022), L2 de \(1\) Mio en \(14\) cycles, L3 de \(32\) Mio en \(50\) cycles.
| Registres | SRAM ({Static RAM}) | DRAM ({Dynamic RAM}) | |
|---|---|---|---|
| technologie | intégrés au processeur | \(6\) transistors par bit | \(1\) transistor et \(1\) condensateur par bit |
| temps d’accès | moins d’un cycle (\(<0{,}3\) ns) | \(1\) à \(2\) ns | \(60\) à \(100\) ns |
| capacité | quelques dizaines (\(32\) à \(64\)) | quelques Mio | \(8\) à \(32\) Gio |
| coût | — | de l’ordre de cent fois la DRAM par Gio | quelques dollars le Gio |
| usage | variables temporaires, pointeurs | caches L1, L2, L3 | mémoire principale (RAM) |
| particularité | — | pas de rafraîchissement | rafraîchissement périodique (d’où « dynamique ») |
Lecture. La SRAM est rapide mais encombrante sur le silicium : elle sert aux caches. La DRAM, dense et bon marché, sert à la mémoire principale. Le prix de la DRAM varie fortement d’une année à l’autre ; les coûts sont des ordres de grandeur.
Énoncé
Un processeur a les caractéristiques suivantes :
Correction
Conclusion : le cache est indispensable ; sans lui, le processeur passerait \(99\,\%\) de son temps à attendre la mémoire.
Piège d’arrondi : diviser par \(0{,}33\) donne \(303\) cycles et un rapport de \(101\) ; l’écart vient seulement de l’arrondi de \(1/3\).
Le cache comble l’écart de la hiérarchie en exploitant deux propriétés des programmes.
Localité temporelle : une donnée lue maintenant sera probablement relue bientôt.
Localité spatiale : après une donnée, ses voisines en mémoire seront probablement lues.
Lecture. Le cache garde les données récemment utilisées (temporelle) et charge des blocs entiers de données voisines (spatiale).
Le cache est organisé en lignes de cache (ou blocs) :
Succès et défaut de cache
Un succès ({cache hit}) : la donnée demandée est dans le cache. Un défaut ({cache miss}) : elle n’y est pas et doit être chargée depuis la mémoire.
Quand le processeur demande une donnée :
défaut :
Lecture. Le processeur demande t[50], à l’adresse \(200\), donc dans le bloc \(3\). Le cache L1 est examiné (orange) : ses quatre lignes contiennent les blocs \(9\), \(12\), \(1\) et \(6\).
Lecture. Le bloc \(3\) n’est dans aucune ligne : c’est un défaut. La demande passe à la mémoire principale.
Lecture. La mémoire envoie le bloc \(3\) entier, \(64\) octets, en environ \(300\) cycles (\(100\) ns à \(3\) GHz). Il remplace la ligne la moins récemment utilisée, celle du bloc \(1\), qui est évincée (rouge : ligne modifiée).
Lecture. La donnée t[50] est lue dans la ligne (orange) et transmise au processeur. Le défaut a coûté environ \(300\) cycles ; pendant ce temps, le processeur attendait.
Lecture. Demande suivante : t[51], adresse \(204\), encore dans le bloc \(3\), maintenant présent dans L1 : succès, en environ \(3\) cycles (\(1\) ns), cent fois moins qu’un défaut.
Énoncé
Un programme lit dans l’ordre un tableau de \(100\) entiers de \(4\) octets.
Question : temps total d’accès aux \(100\) entiers.
Correction
Sans cache : \(100 \times 100 = 10\,000\) ns : le cache divise le temps par \(12{,}6\).
Observation : grâce à la localité spatiale, seulement \(7\,\%\) de défauts. La septième ligne ne contient que \(4\) entiers utiles sur \(16\).
Sortie \(\rightarrow\)
7 93 793
Lecture. \(i=0\) : t[0] est à l’adresse \(0\), dans la ligne \(0\), absente : défaut ; la ligne entière (t[0] à t[15]) est chargée (rouge : modifiée).
Lecture. \(i=1\) : adresse \(4\), toujours la ligne \(0\), examinée (orange) et présente : succès. Le défaut précédent a apporté les voisins : c’est la localité spatiale.
Lecture. \(i=15\) : adresse \(60\), dernier entier de la ligne \(0\) : quinze succès de suite après un seul défaut.
Lecture. \(i=16\) : adresse \(64\), ligne \(1\), absente : deuxième défaut, la ligne \(1\) est chargée. Le motif se répète toutes les \(16\) itérations.
Lecture. Fin de boucle (\(i=99\), adresse \(396\), ligne \(6\)) : \(7\) lignes touchées, donc \(7\) défauts et \(93\) succès ; la ligne \(6\) ne sert qu’à \(4\) entiers sur \(16\).
Lecture. Ligne 10 : \(7\times100+93\times1=793\) ns, contre \(100\times100=10\,000\) ns sans cache : \(12{,}6\) fois moins. Taux de défauts : \(7/100=7\,\%\).
Temps d’exécution sur \(n\) processeurs et accélération ({speedup})
\[ T(n) = T_s + \frac{T_p}{n} + T_{\text{surcoût}} \]
\[ S(n) = \frac{T(1)}{T(n)} = \frac{T_s + T_p}{T_s + \frac{T_p}{n}} \]
\(T_s\) : séquentiel ; \(T_p\) : parallélisable ; \(T_{\text{surcoût}}\) : synchronisation, communication ; seconde égalité sans surcoût ; les labs notent \(p\) le nombre de processus.
Lecture. Seule la partie parallélisable se partage : \(S(4)=100/36{,}5=2{,}74\), au lieu de \(3{,}08\) sans surcoût.
Idéal : \(S(n)=n\), le programme va \(n\) fois plus vite avec \(n\) processeurs.
En pratique, \(S(n)<n\) à cause de :
Lecture. Avec \(f=10\,\%\) de séquentiel, \(16\) processeurs donnent \(6{,}40\), et la courbe ne dépassera jamais \(10\). Chaque courbe plafonne à \(1/f\) (pointillés).
Efficacité
\[ E(n) = \frac{S(n)}{n} = \frac{T(1)}{n \times T(n)} \]
Part du travail des \(n\) processeurs réellement utile.
Exemple
Un programme de \(100\) s dure \(30\) s sur \(4\) processeurs : \(S=100/30=3{,}33\), \(E=3{,}33/4=0{,}83\). Chaque processeur a travaillé utilement \(83\,\%\) du temps ; le reste est perdu en attente et en coordination.
Au-delà de 100 %
Rarement, \(E>1\) : en découpant les données, chaque part tient dans le cache de son cœur (accélération superlinéaire). C’est l’exception.
Énoncé
Un programme s’exécute en \(120\) s en séquentiel, en \(40\) s avec \(4\) processeurs. Calculer :
Correction
Loi d’Amdahl
Soit \(f\) la fraction séquentielle du programme (\(0 \le f \le 1\)) :
\[ S(n) = \frac{1}{f + \frac{1-f}{n}} \]
Accélération maximale (\(n \to \infty\)) : \(S_{\max} = \dfrac{1}{f}\).
Interprétation : si \(5\,\%\) du programme est séquentiel (\(f=0{,}05\)), l’accélération maximale est \(20\), même avec un million de processeurs.
Analogie
La loi d’Amdahl dit une vérité fondamentale : on ne peut pas paralléliser ce qui est intrinsèquement séquentiel. C’est comme une chaîne de production : peu importe le nombre d’ouvriers, si une étape doit être faite par une seule personne, elle devient le goulot d’étranglement.
La formule vient de \(T(n)=T_s+T_p/n\) avec \(T(1)=1\), \(T_s=f\) et \(T_p=1-f\).
Lecture. \(n=1\) : le programme dure \(100\) s, dont \(10\) s de partie séquentielle (violet) et \(90\) s de partie parallélisable (bleu) ; \(S(1)=1\).
Lecture. \(n=2\) : seule la partie parallèle se partage : \(10+90/2=55\) s, \(S(2)=100/55=1{,}82\).
Lecture. \(n=4\) : \(10+90/4=32{,}5\) s, \(S(4)=3{,}08\). La partie séquentielle fait déjà près du tiers du temps.
Lecture. \(n=8\) : \(10+90/8=21{,}25\) s, \(S(8)=4{,}71\). Doubler \(n\) ne fait plus gagner que \(11{,}25\) s.
Lecture. \(n\to\infty\) : \(90/n\to0\), il reste les \(10\) s séquentielles : \(S\to100/10=10=1/f\). L’accélération plafonne à \(10\).
| \(f\) (séq.) | \(S(2)\) | \(S(10)\) | \(S(100)\) | \(S(\infty)\) |
|---|---|---|---|---|
| \(0\,\%\) | \(2{,}00\) | \(10{,}00\) | \(100{,}00\) | \(\infty\) |
| \(5\,\%\) | \(1{,}90\) | \(6{,}90\) | \(16{,}81\) | \(20\) |
| \(10\,\%\) | \(1{,}82\) | \(5{,}26\) | \(9{,}17\) | \(10\) |
| \(20\,\%\) | \(1{,}67\) | \(3{,}57\) | \(4{,}81\) | \(5\) |
| \(50\,\%\) | \(1{,}33\) | \(1{,}82\) | \(1{,}98\) | \(2\) |
Calcul d’une case, \(f=5\,\%\) et \(n=100\) :
\[ S(100)=\frac{1}{0{,}05+\frac{0{,}95}{100}}=\frac{1}{0{,}0595}=16{,}81 \]
Lecture. Avec \(5\,\%\) de séquentiel, \(100\) processeurs ne donnent que \(16{,}81\), à peine \(17\,\%\) de l’idéal. Les petites fractions séquentielles coûtent cher dès que \(n\) grandit.
Énoncé
Un programme a une partie séquentielle de \(15\,\%\).
Correction (\(f=0{,}15\))
La loi d’Amdahl suppose que la taille du problème est fixe. Gustafson (1988) propose une autre vision : quand on a plus de processeurs, on traite des problèmes plus grands.
Les deux lois ne posent pas la même question :
Différence clé :
Loi de Gustafson
Le temps d’exécution est fixe. Si \(s\) est la fraction du temps passé en séquentiel sur \(n\) processeurs :
\[ S(n) = s + n(1-s) = n - s(n-1) \]
Elle mesure combien de travail supplémentaire on accomplit avec plus de processeurs.
Lecture. \(n=1\) : les deux lois partent du même programme, \(0{,}1\) de séquentiel et \(0{,}9\) de parallélisable, pour une durée de \(1\) ; \(S=1\).
Lecture. \(n=2\) : Amdahl garde le problème et partage le \(0{,}9\) : durée \(0{,}55\), \(S=1{,}82\). Gustafson garde la durée \(1\) et donne \(0{,}9\) de travail à chaque processeur : \(0{,}1+2\times0{,}9=1{,}9\), \(S=1{,}9\).
Lecture. \(n=4\) : Amdahl : \(0{,}1+0{,}9/4=0{,}325\), \(S=3{,}08\). Gustafson : \(0{,}1+4\times0{,}9=3{,}7\), \(S=3{,}7=4-0{,}1\times3\).
Lecture. \(n=8\) : Amdahl : \(0{,}2125\), \(S=4{,}71\) ; le violet pèse de plus en plus. Gustafson : \(7{,}3=8-0{,}1\times7\) ; le séquentiel ne pèse plus que \(0{,}1/7{,}3\approx1{,}4\,\%\) du travail.
Énoncé
Avec \(10\,\%\) de code séquentiel et \(16\) processeurs :
Correction
Granularité d’une tâche
Rapport entre calcul et communication d’une tâche :
\[ G = \frac{T_{\text{calcul}}}{T_{\text{communication}}} \]
\(T_{\text{communication}}\) : création, synchronisation, communication. \(G\) fixe la taille des unités de travail parallèle.
Trois types de granularité :
Analogie : un déménagement entre amis
Lecture. Fine : \(10/11=91\,\%\) du temps en surcoût. Moyenne : \(0{,}1/1{,}1=9\,\%\). Grosse : \(12\) processeurs sur \(16\) restent inactifs, efficacité de \(4/16=25\,\%\).
Surcoût ({overhead})
Temps supplémentaire nécessaire pour gérer le parallélisme, qui n’existe pas dans la version séquentielle : c’est le coût de la parallélisation.
Le surcoût comprend :
Lecture. Le Lab 2 mesure ce surcoût : il fait payer le prix des tâches trop petites confiées à plusieurs processus.
Énoncé
Un calcul matriciel se découpe de deux façons :
Avec \(4\) processeurs, quelle option est la meilleure ? Et avec \(100\) processeurs ?
Correction
Avec 4 processeurs
Avec 100 processeurs
Sortie \(\rightarrow\)
303 275 101 11
Lecture. Ligne 7, découpage B sur \(4\) cœurs. Tâche \(1\) : fin\(=[0,0,0,0]\), min désigne le cœur \(0\), qui finit à \(100+1=101\) ms.
Lecture. Tâches \(2\) à \(4\) : chacune va au cœur libre le plus tôt, les cœurs \(1\), \(2\), \(3\) : fin\(=[101,101,101,101]\).
Lecture. Tâches \(5\) à \(8\) : deuxième tour : fin\(=[202,202,202,202]\).
Lecture. Tâches \(9\) et \(10\) : les cœurs \(0\) et \(1\) en font \(3\) et finissent à \(303\) ms, les cœurs \(2\) et \(3\) en font \(2\) et attendent dès \(202\) ms. Répartition \(3/3/2/2\) : B dure \(303\) ms.
Lecture. Ligne 8, découpage A sur \(4\) cœurs : \(250\) tâches de \(1{,}1\) ms par cœur, \(275\) ms, sans attente. A gagne malgré dix fois plus de surcoût total (\(100\) ms contre \(10\)).
Lecture. Lignes 9 et 10, sur \(100\) cœurs : B n’a que \(10\) tâches, \(90\) cœurs restent inactifs et la durée reste \(101\) ms ; A donne \(10\) tâches à chaque cœur : \(11\) ms.
À retenir (1/2)
À retenir (2/2)
Vrai ou faux
Correction
Les labs de la séance
lambda et map, mesure du temps, NumPy, fils et processus.map, reduce, tâches indépendantes, compter les mots de huit textes avec plusieurs processus.multiprocessing, accélération mesurée, prix des tâches trop petites, loi d’Amdahl confrontée à la mesure, \(\pi\) par Monte-Carlo.Prochaine séance
Séance 2 : cohérence de cache et protocoles. Chaque cœur a ses propres caches : quand deux cœurs utilisent la même donnée, comment garantir qu’ils en voient la même valeur ? Protocoles MSI et MESI, false sharing (faux partage), cohérence séquentielle. Lab 3 : la mémoire.
Données de l’évolution des processeurs : K. Rupp, 50 Years of Microprocessor Trend Data (1971–2021, licence CC BY 4.0). Chiffres matériels : Intel (Pentium 4 570J, \(3{,}8\) GHz, novembre 2004) ; AMD, présentation de l’architecture Zen 4, août 2022 (L2 en \(14\) cycles, L3 en \(50\) cycles).
Architecture et Programmation Parallèles — Séance 1 Dr. El Hadji Bassirou TOURÉ • DMI/FST/UCAD
Lecture. La mémoire est une suite de blocs de \(64\) octets : le bloc \(k\) couvre les octets \(64k\) à \(64k+63\). Un tableau
td’entiers de \(4\) octets rangé à partir de l’adresse \(0\) met \(16\) entiers par bloc.Lecture.
t[50]est à l’adresse \(4\times50=200\) ; son bloc est \(\lfloor 200/64\rfloor=3\) (orange : examiné), qui couvre les octets \(192\) à \(255\), soitt[48]àt[63].Lecture. Un défaut sur
t[50]charge le bloc entier : ses \(64\) octets sont copiés dans une ligne du cache (rouge : modifiée), avect[48]àt[63]; les \(15\) voisins sont désormais en cache.