Multicoeurs, Hyperthreading, parallélisme : qu'estce que c'est ?

Par Guy Grave (Mewtow)

www.siteduzero.com

Licence Creative Commons BY-NC-ND 2.0 Dernière mise à jour le 29/09/2012

2/97

Sommaire
Sommaire ........................................................................................................................................... 2 Lire aussi ............................................................................................................................................ 2 Multicoeurs, Hyperthreading, parallélisme : qu'est-ce que c'est ? ...................................................... 4 Partie 1 : Le parallélisme : un peu de théorie ...................................................................................... 5
Les différentes architectures parallèles ............................................................................................................................ 5
Les différents types de parallélisme ............................................................................................................................................................................ 5 Parallélisme de Threads .............................................................................................................................................................................................. 5 Parallélisme d'instruction ............................................................................................................................................................................................. 5 Parallélisme de données ............................................................................................................................................................................................. 6 Taxonomie de Flynn .................................................................................................................................................................................................... 6 SISD ............................................................................................................................................................................................................................ 6 SIMD ........................................................................................................................................................................................................................... 6 MISD ........................................................................................................................................................................................................................... 7 MIMD ........................................................................................................................................................................................................................... 8 Résumé ....................................................................................................................................................................................................................... 8 Partage de la mémoire ................................................................................................................................................................................................ 8 SASM .......................................................................................................................................................................................................................... 8 DADM .......................................................................................................................................................................................................................... 9 SADM ........................................................................................................................................................................................................................ 10

Les limites théoriques ...................................................................................................................................................... 11
Loi d'Amdhal .............................................................................................................................................................................................................. 12 Hypothèses de base .................................................................................................................................................................................................. 12 Gain ........................................................................................................................................................................................................................... 13 Dérivation de la Loi d'Amdhal .................................................................................................................................................................................... 13 Ce que nous dit la loi d'Amdhal ................................................................................................................................................................................. 14 Parallélisons mieux ! ................................................................................................................................................................................................. 14 Nombre de processeurs ............................................................................................................................................................................................ 15 Code série ................................................................................................................................................................................................................. 16 Loi de Gustafson ....................................................................................................................................................................................................... 16 Parallélisme de données ........................................................................................................................................................................................... 16 Hypothèses de base .................................................................................................................................................................................................. 17 Gain ........................................................................................................................................................................................................................... 17 Que nous apprend Gustafson ? ................................................................................................................................................................................ 18

Partie 2 : Multi-processeurs, Multicoeurs et Hyperthreading ............................................................. 18
Hyperthreading et compagnie ......................................................................................................................................... 19
Du parallèlisme avec un seul processeur ................................................................................................................................................................. 19 Le pipeline fait des bulles ! ........................................................................................................................................................................................ 19 Types de super-threading matériel explicite .............................................................................................................................................................. 20 Processeurs superscalaires ...................................................................................................................................................................................... 25 Et dans le processeur ? ............................................................................................................................................................................................ 26 Cycle Fetch / Exécution ............................................................................................................................................................................................. 26 Niveau circuits ........................................................................................................................................................................................................... 27

Processeurs multicoeurs ................................................................................................................................................. 30
Processeurs multicœurs ........................................................................................................................................................................................... 31 Le multicœurs, c'est quoi ? ....................................................................................................................................................................................... 31 Multicœurs asymétrique ............................................................................................................................................................................................ 31 Cluster Multithreding ................................................................................................................................................................................................. 32 Le partage des caches .............................................................................................................................................................................................. 33 Caches dédiés versus caches partagés ................................................................................................................................................................... 33 La réalité .................................................................................................................................................................................................................... 35 Communication inter-processeurs ............................................................................................................................................................................ 36 L'exemple du x86 ...................................................................................................................................................................................................... 37

Partie 3 : Le partage de la mémoire .................................................................................................. 38
Cohérence mémoire ........................................................................................................................................................ 39
La cohérence : c'est quoi ? ....................................................................................................................................................................................... 39 Mécanismes de base ................................................................................................................................................................................................ 40 Politique d'écriture du cache ..................................................................................................................................................................................... 40 Caches partagés ....................................................................................................................................................................................................... 43 Direct Memory Acces ................................................................................................................................................................................................ 44 Conclusion ................................................................................................................................................................................................................ 44 Protocoles de cohérence des caches ....................................................................................................................................................................... 44 Protocole MSI ............................................................................................................................................................................................................ 44 Protocole MESI ......................................................................................................................................................................................................... 46 Protocole MOESI ....................................................................................................................................................................................................... 46 Implémentation .......................................................................................................................................................................................................... 47 Directory Protocol ...................................................................................................................................................................................................... 47 Snooping protocols ................................................................................................................................................................................................... 47 Write Invalidation et Write Broadcasting ................................................................................................................................................................... 48

Consistance mémoire ..................................................................................................................................................... 50
Problèmes ................................................................................................................................................................................................................. Accès simultanés ...................................................................................................................................................................................................... Memory Ordering ...................................................................................................................................................................................................... Modèles de consistance ............................................................................................................................................................................................ Séquential Consistency ............................................................................................................................................................................................. 50 50 51 51 51

www.siteduzero.com

.............................................................................................................................................................................................................................. 53 Load/Store Fences ..................................................................................................................................................................................................................................... 59 Speculative Lock Elision ....... SSE et AVX ........................ 86 Quelques registres utiles .......................... 62 Mémoire cache ................................................................................................................................................................................ 69 Partie 5 : Le parallèlisme de données ............................................................................................................................................................................................................................................................................................ 85 Accès mémoires ............................................................................................. C'est quoi ? .................................................. 83 Solutions ..................................................................................................................................................................................................................................................................................................................... 65 C'est quoi ? .......................................................................................... 58 Instructions LL/SC ......................................................................................................................................................................................................................................................................................................................... 65 Architectures Distribuées ............................. 82 Startup et Dead Time .................siteduzero................. 86 Patterns d'accès mémoire .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. Vieux GPUs ................................................................................................................................................................................................ 74 Instructions SIMD ........................................................................................................................................................................................................ 54 Et pour le programmeur ? ....................................................................................................................................................................................................................................... AVX ................................................................................................................................................................................................................................... 81 Processeur vectoriels Memory-Memory ..................................................................................................................com ................... SSE ..................................................................................................................................................................... 62 Prefetcher Contention ............................................................................................................................................................................................................................................................................................................................................................................................... 59 L'exemple avec le x86 ...................................... Pas de cache de données ........................................................................................................................................................................................ 56 Instructions atomiques .................................................................................................................................................................................................................... Exemples : MMX................................................................................................... 53 Full Fence ............................................. 88 Vector Mask Register .......................................................................................................................................................................................................................................................................... 58 Mémoire Transactionelle Matérielle .............................................................. Prédication ................................................................................ 81 Pipelining .......................................................................................................................................... Des besoins en terme de sureté ....................................................................................................................................................................................................................................................................................................................................................................................................................................... le retour ! .................................................................................................................................................................................. Noeuds .............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. 90 90 90 91 92 92 93 94 94 95 95 www.................................................................. 63 Cache Contention ............................................................................................................................................................................................ Une histoire de latence .................................................................................................................................................................. 74 75 76 76 78 80 Les processeurs vectoriels ................................................................................................................................................................... 65 66 66 67 67 67 70 70 70 71 71 71 72 NUMA .................................................................................................. 84 Chaining ................................................. 89 Architecture d'un GPU ............................... Différents types ................... Du coté de la RAM ................................................................................................................................................................................................................. Directory Protocol ..... MMX ..............................................................................................Sommaire 3/97 Relaxed Consistency ................................................................................................................... 56 Exclusion mutuelle ..................................................................................................................................................................................................................................... Communication inter-processeur .................................................................................................... Organisation de base ................................................................................................................................................................................................... 89 GPGPU et Streams Processors .......................................... Jeu d'instruction .................................................................................. 85 Pipelining des accès mémoires .................................................................................. 57 Problèmes avec les verrous d'exclusion mutuelle ......................................................................................................... Register Files ............................................................................................................................................................................................................................. De quoi NUMA est-il ne nom ? ................................................................................................................. 64 Partie 4 : Architectures NUMA et distribuées ................................................................................................................................................................................................................................................................................................................................................................................................. 85 Multiple Lanes ..................................................................................................................................................................................................................................... 51 Exemples ....................................................................................... Hiérarchie mémoire ........................................................................................................... 88 Vector Length Register ................................................ Message Passing ................................................................. 54 Synchronisation entre Threads ............. 63 False Cache Sharing .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... 62 Bus mémoire . Arithmétique saturée ............................................................................................................................................................................................................................................................................................. 74 Instructions SIMD .................................................................................................................................................................................................................................... 81 Processeurs vectoriels Load-Store ............................................................................................ Les réseaux inter-processeurs ................................................................................................................................... 64 Solutions ....................................................................................................................................................................................................................................................... 80 Architectures ............................ Directory .......................................................................................................................................................................................................................................... 62 Bus Contention ................................................................... 52 Fences et Memory Barrier ......................................................................................................................................................................................................................................................................................................................................... 56 Sections Critiques ..................................... 58 Versions évoluées ........................................................ Cohérence des caches........ Stream Processors ....................................................................................................................................................................................................................................................................................................................................................... Plusieurs ALU .................................................................................................................................................................................................................................................................................................................................................................................................................... 60 Partage des ressources ............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Sommaire 4/97 Multicoeurs.siteduzero. Cette solution qui consiste à répartir des calculs sur plusieurs processeurs s'appelle le parallélisme. Entre les ordinateurs embarquant plusieurs processeurs. Cela fait quelques années que de tels processeurs ont étés mis sur le marché et sont accessibles à tout un chacun moyennant une certaine somme d'argent (très souvent trop assez conséquente). L'utilité de tels processeurs est très simple : la performance ! De tels processeurs permettent de faire exécuter des instructions indépendantes dans des processeurs séparés. il n'est pas rare de posséder des processeurs contenant plusieurs cœurs. les processeurs vectoriels et autres. le monde du parallélisme vous attend. avec l’avènement des architectures multicœurs. Hyperthreading. de nombreux programmeurs cherchent à utiliser au mieux la puissance fournie par les processeurs modernes.com . 3 heures. En effet. www. 3 jours. Mais rassurez-vous : ce tutoriel est là ! Grâce à ce tutoriel. Nous parlerons aussi bien du fonctionnement de ces processeurs et ordinateurs que des interactions entre matériel et logiciel : préparez-vous. les architectures dataflow . vous aurez un aperçu des différentes architectures parallèles et vous verrez leurs forces et leurs faiblesses. 7 minutes 2 020 visites depuis 7 jours. il y a de quoi être perdu assez rapidement. parallélisme : qu'est-ce que c'est ? Par Guy Grave (Mewtow) Mise à jour : 26/09/2012 Difficulté : Facile Durée d'étude : 1 mois. Mais les processeurs multicœurs ne sont pas les seuls processeurs permettant de faire ceci : de nombreux autres processeurs et architectures permettent d’exécuter plusieurs calculs simultanément. classé 69/792 De nos jours.

mais dans un sens plus général. nous verrons un peu quel est le but des différentes architectures que nous verrons dans ce tutoriel. qui sont des descendants directs de nos architectures dataflow . ainsi que quelques autres processeurs un peu spéciaux. éventuellement grâce à des indications fournies par le programme lui-même ! On peut ainsi. Allez donc lire la partie 7 si vous vous en sentez le courage et que vous en avez les www. qui incorporent des techniques comme l'Out Of Order et autres renommages de registres. je parlerai de threads matériels .) un moyen de créer des ordinateurs de ce genre.. certains processeurs sont capables de découper un programme à l’exécution. le compilateur peut s'en charger tout seul. à partir d'un programme unique non-découpé en threads. la plus évidente. Pour cela. Parallélisme de Threads La première solution. Les architectures dataflow furent une de ces réponses. on serait rapidement perdu. et qui devra utiliser le processeur. exécutables en parallèle si le matériel le permet. Attention : j'utilise le terme thread non dans le sens thread logiciel. il suffit de découper notre programme en plusieurs sous-programmes indépendants qu'on peut faire éxecuter en parallèle. tous plus bizarres que les autres. Pour le moment. parallélisme : qu'est-ce que c'est ? 5/97 Partie 1 : Le parallélisme : un peu de théorie Commençons par les bases et expliquons ce qu'est le parallélisme. Dans certains cas. Dans ce chapitre. etc. le découpage d'un programme en threads est avant tout un problème logiciel. l'Out Of Order. qui ne cherchent pas à paralléliser la même chose. Nos langages de programmation disposent souvent de mécanismes permettant de découper notre programmes en threads logiciels. Les concepteurs de processeurs on en effet inventé des tas de techniques permettant à notre processeur de ne pas exécuter des instructions dans l'ordre prévu par le programmeur : le pipeline. considérez qu'un thread est simplement un morceau de programme. utiliser plusieurs cœurs ou processeurs sans problèmes ! Le découpage en thread se fait alors à l’exécution. Ces sousprogrammes indépendants sont ce qu'on appelle des Threads. et particulièrement dans les chapitres sur les processeurs multithreadés un peu spéciaux. à la condition que ces instructions appartiennent à un seul et unique programme. Pour se faciliter la tâche. diverses classifications ont été inventées pour mettre un peu d'ordre dans tout ce foutoir. Je préfère prévenir tout de suite. Les différentes architectures parallèles Il existe différents types d'ordinateurs parallèles. L'imagination des chercheurs en architectures de ordinateurs a en effet été mise à rude épreuve devant le défi que nous imposait la création des architectures parallèles et de nombreuses architectures ont étés inventées pour répondre à ce besoin. Ce découpage est donc dans ce cas du ressort du compilateur ou du programmeur : c'est à eux de modifier le programme pour le paralléliser. même si cela a déjà été tenté : on reviendra dessus quand je parlerai des architectures EDGE et du spéculative multithreading dans ce tutoriel.Multicoeurs. pour commencer.siteduzero. Quand je parlerai de threads. Sachez juste que cette forme de parallélisme n'est pas au programme de ce tutoriel : le tutoriel Fonctionnement d'un ordinateur depuis zéro se charge déjà d'expliquer certaines de ces techniques. largement utilisé dans la littérature sur l'architecture des ordinateurs parallèles. et que créer des architectures réellement conçue pour exécuter des instructions en parallèle serait une grande avancée. plus étonnant. j'utiliserai le terme thread de cette façon. vu que dans la suite du tutoriel. Il suffit de faire exécuter chaque Thread sur un processeur séparé pour pouvoir paralléliser le tout. la création de processeurs superscalaires. bien que cela soit plus rare. Et enfin. Créer des architectures spéciales serait donc un gros plus qui permettrait non pas de découper des programmes entiers en morceaux qu'on pourrait exécuter en parallèle. Avec ce genre de parallélisme. Ces techniques permettent à un processeur seul d’exécuter plusieurs instructions simultanément. Les architectures permettant d’exécuter des threads en parallèle sont donc des architectures multiprocesseurs ou multicœurs. et ce de façon optimale en fonction du nombre de processeurs.. sans trop d'aide venant du logiciel. Hyperthreading. mais permettrait de paralléliser directement un programme au niveau de ses instructions ! Nos chercheurs ont cherché (quoi de plus normal. Les différents types de parallélisme Il faut savoir qu'il existe différentes formes de parallélisme.com . constitué d'une suite d'instructions à exécuter en série. Si on devait se lancer sans chercher à organiser le tout. Parallélisme d'instruction Mais certains chercheurs se sont dit que penser hors du cadre ne faisait pas de mal : ceux-ci ont considéré que paralléliser un programme pouvait être un problème matériel. consiste simplement à exécuter des calculs indépendants en parallèle. Les techniques apprises sur les ordinateurs dataflow ont malgré tout été reprises dans les processeurs modernes. Mais nous ne parlerons pas de ces architectures spéciales dans ce tutoriel. Mais cela est tout de même assez rare.

Cela permet donc de traiter N données sur N processeurs en même temps. bien au contraire : la quasi-totalité des processeurs est aujourd'hui de ce type. ainsi que leurs confrères de chez ARM. on va maintenant voir les différents types d'architectures parallèles.siteduzero. comme on le verra plus tard : il n'y a pas vraiment de limitations théoriques à ce genre de parallélisme. mais chacun de ces processeurs va travailler sur une donnée différente. Celles-ci peuvent exécuter une instruction sur plusieurs données à la fois. et certaines architectures ne rentrent pas vraiment dans les catégorie de la classification de Flynn. MIPS et VIA en sont capables. qui sont des architectures permettant d'exploiter le parallélisme de données. Aussi. tous les processeurs Intel et AMD actuels. SIMD. Ce sont les ordinateurs SISD (Single Instruction Single Data ). Cette classification a remarquablement tenue le coup au fil du temps : on a beau eu inventer des tas d'architectures plus bizarres les unes que les autres. Taxonomie de Flynn Maintenant qu'on connait un peu les différentes formes de parallélisme. Elle n'est pas parfaite. mais ce n'est qu'un détail que je me permets de passer sous silence. est d'une grande aide au quotidien (ou presque). bien que simpliste. Plus précisément. Les processeurs pouvant faire ce genre de chose ne sont pas rares. Cette solution est celle qui est la moins limitée. cette classification n'en est pas moins restée consistante et redoutablement fiable. je me permets de vous parler de cette classification qui. un scientifique américain assez connu dans le milieu du hardware qui se nomme Flynn a classé ces architectures en 4 grandes catégories : SISD. www. Le parallélisme de donnée est aussi massivement utilisé dans les cartes graphiques. et MIMD. et rien d'autre. Ceux-ci vont exécuter une instruction sur un seul ensemble de données. qui sont des composants devant exécuter les mêmes instructions sur un grand nombre de données : chaque calcul sur un pixel est plus ou moins indépendant des transformations qu'on effectue sur ses voisins. Dans les années 1966.Partie 1 : Le parallélisme : un peu de théorie compétences ! 6/97 Parallélisme de données Autre solution. Pour résumer : tous les processeurs exécutent un seul et unique programme ou une suite d'instructions. SIMD Vient ensuite le tour des architectures SIMD (Single Instruction Multiple Data ). MISD. exécuter le même programme sur des données différentes et indépendantes.com . SISD Le premier type d'ordinateur correspond aux processeurs purement séquentiels et incapables de toute forme de parallélisme.

Cette catégorie d’architectures est vraiment très rare. comme on le verra plus tard.siteduzero. mais sur des données différentes. C'est aussi le cas de pas mal de cartes graphique. V ous verrez par exemple que nos processeurs un tant soit peu récents possèdent des instructions machines capables d'effectuer des calculs sur plusieurs données différentes à la fois.com .Partie 1 : Le parallélisme : un peu de théorie 7/97 On verra ces types d'architectures en détail dans notre tutoriel. Ceux-ci peuvent exécuter des instructions différentes en parallèle sur une donnée identique. sur lesquelles chaque processeur exécute la même instruction que ses collègues. Nous verrons aussi des architectures composées de plusieurs processeurs. ce qui les classe d'office dans la catégorie SIMD. www. MISD Vient ensuite le tour des ordinateurs MISD (Multiple Instruction Single Data ). Autant prévenir tout de suite : on ne verra aucun exemple de ce type dans le tutoriel. On peut citer comme exemples d'architectures MISD les architectures systoliques et cellulaires.

Aussi. Vient ensuite le Multiple Program Multiple Data . Elle marche pour la grosse majorité des architectures parallèles que nous allons voir dans ce tutoriel.siteduzero. on se retrouve avec une seule et unique mémoire partagée entre tous les processeurs. les cas spéciaux méritant un article à eux tout seuls.Partie 1 : Le parallélisme : un peu de théorie 8/97 MIMD Et enfin. Suivant la façon dont est partagée la mémoire. On peut préciser que cette catégorie MIMD peut être découpée en deux sous-catégories. on peut parfaitement exécuter des morceaux de programmes différents sur des données différents. Le temps mit pour accéder à la mémoire est identique pour tous les processeurs : on parle alors d'Unifed Memory Access ou encore d'UMA. voici la classe la plus importante : les architectures MIMD (Multiple Instruction Multiple Data ). aussi appelé SPMD : cela consiste à exécuter un seul programme sur plusieurs données à la fois. Le SIMD force à exécuter la même instruction sur plusieurs données. Celles-ci peuvent éxecuter des instructions différentes sur des données différentes. www. SASM Dans le premier cas. qui consiste à exécuter des programmes en parallèle sur des données différentes. on pourrait croire qu'il s'agit de SIMD. il est important de savoir à comment est partagée la mémoire. mais est tout de même redoutablement efficace. Mais il y a une différence : avec le SPMD.com . Nos processeurs multicœurs et multiprocesseurs font partie de la catégorie MIMD. La première est le Single Program Multiple Data . Peut traiter : Une instruction à la fois Plusieurs instructions différentes à la fois Un seul ensemble de données à la fois Plusieurs ensembles de données à la fois SISD MISD SIMD MIMD Partage de la mémoire La capacité à traiter des données ou des instructions différentes simultanément n'est pas la seule différence entre les architectures parallèles : la façon dont les processeurs doivent se partager la mémoire est aussi très importante. de nombreux problèmes peuvent apparaitre. V oyons un peu les différentes possibilités de partage de la mémoire. Résumé Cette classification peut sembler simple. Dit comme cela.

qui jouent souvent de mauvais tours. Imaginons que deux processeurs manipulent une donnée : ceux-ci ont une copie de la donnée dans leur cache qu'ils manipulent et modifient à loisir. Sans compter certaines contraintes concernant la hiérarchie mémoire. Seul problème : la mémoire ne peut lire ou écrire qu'une donnée à la fois (enfin sur des mémoires normales : on passe sous le tapis le cas des mémoires multiports). et permet ainsi des gains assez conséquents. Autant prévenir tout de suite : je parlerai beaucoup des mémoires caches et des problèmes qu'elles peuvent poser dans ce tutoriel. le matériel se charge alors de fournir quelques instructions pour faciliter la communication ou la synchronisation entre les différents morceaux de programmes (interruptions inter-processeurs. qui passe beaucoup de temps à attendre la mémoire. etc). DADM www. C'est pour limiter la casse que l'on a inventé ces fameux caches. elle sera alors différente de celle présente dans le cache de l'autre processeur. De manière générale. Cela permet d’accélérer l'accès à la mémoire partagée. Ce qui fait qu'un processeur peut continuer à manipuler une donnée périmée qui vient d'être mise à jour par l'autre processeur. Malheureusement. imaginez qu'un processeur aille modifier une donnée qui est en cours de traitement par un autre processeur : on peut aller rapidement vers une belle catastrophe et il est facile de se retrouver avec des données écrasées. Par exemple. Et avec des caches ? Il n'est pas rare que l'on améliore l'architecture SASM en rajoutant ce qu'on appelle des mémoires caches . ajouter un ou plusieurs caches sur des architectures SASM entraine l'apparition de quelques petits problèmes lorsque deux processeurs doivent écrire au même endroit en mémoire. les mémoires caches posent de sacrés problèmes sur pas mal d'architectures parallèles.com .Partie 1 : Le parallélisme : un peu de théorie 9/97 Avec ce genre d'architecture. Si un processeur modifie cette copie de la donnée et que celle-ci est enregistrée dans son cache ou en mémoire. rien n’empêche à plusieurs processeurs de vouloir accéder à la mémoire en même temps.siteduzero. Mais on abordera le sujet en détail plus tard. c'est que les couts de synchronisation et de communication entre les différents morceaux de programmes peut être assez conséquent et peut réduire les performances si on s'y prend assez mal : ce partage de la mémoire est assez embêtant. instructions machines atomiques permettant d'implémenter des Mutex. mises à jour trop tôt. La conséquence. ou pire. Il va donc falloir trouver des moyens pour arbitrer les accès à la mémoire entre les processeurs pour éviter les problèmes. Il s'agit simplement de mémoires intercalées entre la RAM de l'ordinateur et le processeur. Il faut dire que la mémoire RAM est vraiment plus lente que le processeur. mais aussi plus rapides et permettent ainsi d’accéder plus rapidement à la mémoire RAM de l'ordinateur. Ces mémoires caches sont plus petites. Et ça peut poser quelques problèmes ! Un processeur doit toujours éviter de se retrouver avec une donnée périmée et doit toujours avoir la valeur correcte dans ses caches : cela s'appelle la cohérence des caches . Pour éviter ce genre de situations fâcheuses.

Mais ils peuvent aussi accéder aux autres mémoires : cela leur prend un peu plus de temps.com .siteduzero. aussi appelées les architectures distribuées. Les processeurs peuvent ainsi accéder à la mémoire d'un autre processeur via le réseau local : il leur suffit de faire une demande au processeur qui détient la donnée. qui leur sert à échanger des données ou des ordres un peu particuliers. et que ceux-ci sont loin d'être négligeables. Tous les processeurs sont reliés entre eux via un réseau local. Avec elles. les processeurs possèdent une mémoire locale. sans aucune mémoire partagée entre les processeurs. chaque processeur possède sa propre mémoire. rien n’empêche de mettre des mémoires caches entre la mémoire d'un processeur et celui-ci. il reste une dernière classe d'architectures : les architectures SADM. qui leur est réservée et dans laquelle ils peuvent faire ce que bon leur semble. Avec une organisation de ce genre. www. Encore des caches Bien sûr. Cette fois. la qualité et les performances du réseau reliant les ordinateurs est très important pour les performances. Avec elles. Cette demande va traverser le réseau local et arriver à son destinataire : la donnée demandée est alors envoyée via le réseau local et est copiée dans la mémoire locale de l’ordinateur demandeur. V oici un exemple d'architecture NUMA possible.Partie 1 : Le parallélisme : un peu de théorie 10/97 Viennent ensuite les architectures DADM. Il va de soit que les communications entre les différents processeurs peuvent prendre un temps relativement long. SADM Enfin. placer des mémoires caches ne pose strictement aucun problème : on n'a pas besoin de garantir la cohérence des caches avec ce genre de système.

www. encore et toujours ! On peut encore une fois utiliser des mémoires caches sur ce genre de machines. Accéder à une portion de cette mémoire unique correspondant à sa mémoire locale est rapide. Des caches.com . comme pour les architectures SASM. mais accéder à une portion de la grosse mémoire unique correspondant à la mémoire d'un autre processeur est redoutablement plus lent. mais on retombe sur un problème : la cohérence des caches n'est pas assurée et on doit se débrouiller pour qu'elle le soit.Partie 1 : Le parallélisme : un peu de théorie 11/97 Avec cette méthode. Bien gérer les temps d'accès aux différentes mémoires est donc un plus bien agréable. chaque processeur voit toute les mémoires virtuellement rassemblées dans une seule grosse mémoire unique.siteduzero.

noté . plus notre programme profitera de la présence de plusieurs processeurs. on se doute que plus la quantité de ce code parallèle est importante. Bref. 20 . voir 10 000 000 processeurs. histoire de comparer ce qui est comparable. Intuitivement. on se moque des coûts induits par la gestion du code s’exécutant sur plusieurs processeurs. moins notre programme gagnera en performances si l'on augmente le nombre de processeurs. le code série. est donc la somme : Plusieurs processeurs Maintenant. et on passe tous ces détails sous le tapis. il va nous falloir une référence pour pouvoir comparer les gains dû à l'ajout de processeurs. Un seul processeur Pour commencer. Pour calculer le gain maximal que l'on peut obtenir sur processeurs. il n'y a pas vraiment de limites théoriques aux gains de performances que l'on peut obtenir en ajoutant des processeurs. que de 2 . et du temps d’exécution du code parallèle sur un seul processeur. on va prendre plusieurs processeurs et comparer. Le temps mis par notre programme pour s’exécuter sur un seul processeur. voire à rien. Expliquons un peu plus en détail la deuxième hypothèse. incapable d'exploiter plusieurs CPU. on va supposer : que notre ordinateur contient un nombre de processeurs. Et bien pour connaître ce gain maximal.com . qu'une portion plus ou moins importante de notre programme peut utiliser plusieurs processeurs . De plus. On va appeler le reste du programme. la façon dont est conçu le matériel. . On y verra que dans certains cas. et ces données ont toujours la même taille. Une portion de notre programme peut exploiter plusieurs CPU : on l'appelle le code parallèle. le système d’exploitation. vous ne savez pas trop quel sera le gain que vous obtiendrez en utilisant plusieurs processeurs avec un programme pareil. www. Mais existe-t-il des limites à l'utilisation de plusieurs processeurs ? Dans ce chapitre. on devine facilement que plus le programme contient de code série. on suppose que ce code parallèle peut tout aussi bien exploiter la puissance d'un seul processeur. Mettre plusieurs CPU n'accélérera que cette portion du programme et pas le reste. mais pas au-delà. les résultats changent du tout au tout. Là encore. vous pouvez utiliser la loi d'Amdhal .Partie 1 : Le parallélisme : un peu de théorie 12/97 Les limites théoriques On a vu dans le chapitre précédent que l'on pouvait donc utiliser plusieurs processeurs pour traiter des taches ou des données. ce programme est exécuté sur la même quantité de données : on ne rajoute pas de données en même temps qu'on ajoute des processeurs. Cette loi est basée sur une approche simple : on prend un programme et on regarde ce qui se passe en fonction du nombre de processeurs. qu'on notera du temps d’exécution du code série. et vous voudriez savoir quel est le gain théorique maximal que vous pourriez obtenir. ce code est quasiment parfait et on s'interdit les situations du style : on observe un gain énorme avec 2 ou 4 processeurs. c'est le cas où l'on utilise un seul processeur. Hypothèses de base Pour démontrer cette loi. on va voir que suivant la manière utilisée pour exploiter plusieurs processeurs. V otre programme est conçu de façon à répartir ses calculs sur autant de processeurs que possible. La meilleure référence. Dans d'autres. Seul problème. Loi d'Amdhal Imaginez que vous ayez codé un programme. spécialement conçu pour exploiter plusieurs processeurs. cette utilisation est la plus efficace possible quel que soit le nombre de processeurs. ajouter des processeurs ne sert pas à grand chose.siteduzero. noté .

il suffit de diviser le temps d’exécution obtenu avec un seul processeur par le temps d’exécution sur N processeurs. 3. au lieu de les exécuter les uns après les autres. Exemple Si le temps de calcul avec. on peut aussi dire que le temps de calcul avec un processeur est en effet 2 fois plus long que le temps de calcul avec 5. On verra ainsi si le programme s’exécute 2. pas un de plus. on obtient donc : On rappelle que est le temps mis pour exécuter le code parallèle sur un processeur. Bien sûr. Le temps d’exécution du code parallèle sera alors divisé par le nombre de processeurs . le code série n'est pas affecté par le nombre de processeurs : celui-ci est exécuté sur un seul processeur et met donc toujours le même temps à s’exécuter : ne change pas ! En notant le temps mis par notre code à s’exécuter sur N processeurs. voir 50 fois rapidement. plus ce gain est élevé. Gain Mais que faire pour comparer le temps d’exécution du programme sur la machine avec un processeur et celui mesuré sur une machine avec plusieurs processeurs ? Pour cela. pas un de moins ! V oyons ce qui se passe : le code parallèle va faire exécuter des calculs simultanément sur ces N processeurs. ce qui nous donne : En clair. Ce gain a une signification simple : si le gain vaut X. mettons 5 processeurs (donc N = 5) est la moitié du temps de calcul obtenu avec un seul processeur. Bien évidemment. Ce gain se calcule en inversant la fraction vue au-dessus. alors l'application est X fois plus rapide que si on l’exécutait sur un seul processeur.com . pas . Mais que vaut le temps d’exécution du code parallèle sur N processeurs ? Pour l'évaluer. Cela se vérifie en calculant simplement notre gain : Dérivation de la Loi d'Amdhal www. on va devoir calculer un truc que l'on appelle le gain.Partie 1 : Le parallélisme : un peu de théorie Mais combien va-t-on prendre de processeurs ? 13/97 processeurs. plus notre programme aura gagné en performance comparé à la version avec un seul processeur.siteduzero. cela veut dire que Dans ce cas. Vu que le temps de calcul avec 5 processeurs est égal à la moitié du temps de calcul avec un seul processeur. on n'a pas vraiment le choix : on doit utiliser une des hypothèses de départ qui dit que notre code va répartir au mieux ses instructions sur les processeurs. Le temps passé à exécuter ce code parallèle va diminuer. on peut aussi dire que le programme va deux fois plus vite.

On trouve donc le rapport entre et qui vaut Cette équation peut se "simplifier" et donne alors l'équation suivante Et là. www.com . Mais que peut-on en déduire d'utile ? Peut-on trouver des moyens de gagner en performance efficacement grâce à cette loi ? Oui. est le pourcentage de temps mis à exécuter le code parallèle.Partie 1 : Le parallélisme : un peu de théorie Partons de notre fraction 14/97 Remplaçons T(N) par sa valeur explicitée plus haut à savoir . De même. il faut remarquer deux choses très simples : n'est rien d'autre qu'un pourcentage : c'est le pourcentage de temps mis à exécuter le code série. avec processeurs. et c'est ce qu'on va voir. En posant et .siteduzero. Ce que nous dit la loi d'Amdhal Cette loi nous donne donc une estimation du gain en temps d’exécution d'une application exécutée sur plusieurs processeurs. on peut alors simplifier notre equation en On peut alors calculer le gain en inversant notre fraction et on trouve On peut aussi remarquer que et ainsi obtenir Cette équation s'appelle la loi d'Amdhal et nous donne la gain maximal théorique que l'on peut obtenir avec un code passant pourcents de son temps d’exécution dans un code parallèle.

11% 15.21% . Nombre de processeurs Gain maximal 2 3 4 5 6 7 8 . on obtient alors la tableau suivant. Mais cette solution a une efficacité assez limitée : il faut que la part de code parallélisable soit suffisante pour que cela ait un impact suffisant. La solution la plus simple est donc de paralléliser le plus possible le code de notre programme. Avec N processeurs.6% 21. 25% www. C'est cela qui est le plus recherché à l'heure actuelle. le gain atteint un maximum qui vaut Qu'on peut simplifier en Cela signifie une chose très simple : quand tend vers l'infini. C'est donc la première limite que nous impose la loi d'Amdhal. Seul problème : tous les programmes ne se laissent pas paralléliser aussi facilement. Seul reste le code série qui ne peut pas être accéléré par plusieurs processeurs.38% 17. 11. le temps d’exécution du programme ne peut pas descendre en-dessous du temps d’exécution du code série. Le temps d’exécution de ce code restant le même. histoire de faire diminuer et augmenter le plus possible. Nombre de processeurs L'autre solution est d'augmenter le nombre de processeurs. le code parallélisé est exécuté en un temps qui tend vers ..Partie 1 : Le parallélisme : un peu de théorie 15/97 Parallélisons mieux ! Tout d'abord remarquons une chose : quand on fait tendre le nombre de processeurs vers l'infini. le gain calculable par la loi d'Amdhal nous donne un gain maximal de Si on calcule le gain en fonction du nombre de processeurs.04% 20% 20. Imaginons un exemple simple : 20% du temps d’exécution de notre programme (quand il est exécuté sur un seul processeur) est passé à exécuter du code parallèle. Certains programmes se parallélisent facilement parce que les calculs qu'ils ont à faire sont assez indépendants. voire pas du tout. Mais d'autres n'ont pas cette particularité et sont très très difficilement parallélisables..siteduzero.64% 19.. 16 .. 23% ...com .. afin de réduire le plus possible le terme ..

ça ne marche plus ! Au-delà de 10 processeurs avec un code passant 20% de son temps à exécuter du code parallèle. 8 processeurs.Partie 1 : Le parallélisme : un peu de théorie 16/97 On voit bien qu'au delà de 5 ou 6 processeurs. voir 8 processeurs mais pas audelà. Nombre de processeurs Gain maximal 2 4 8 5% 8% 10% 11. Au lieu de toujours utiliser la même quantité de données. Loi de Gustafson Parallélisme de données La loi d'Amdhal est basée sur une approche simple : on prend un programme qui fait ce qu'il y a à faire. au-delà de 4-8 processeurs. pourquoi ne pas utiliser nos processeurs pour travailler sur plusieurs images de même taille ou sur une image de taille quadruple. Pour donner un exemple. Mais n'y aurait-il pas une autre façon de faire. Code série Une autre solution. Pour prendre un autre exemple. le gain est négligeable. La loi d'Amdhal donne une borne théorique maximale au gain apporté par la présence de plusieurs processeurs. Remarquons que les programmes qui passent la moitié de leur temps à exécuter du code parallèle sont rares chez les programmes grand-public. mais le gain réel sera quasiment toujours inférieur au gain calculé par la loi d'Amdhal. on va faire un petit tableau contenant le gain obtenu avec un programme avec un P de 10% en fonction du nombre de processeurs. pourquoi ne pas simplement utiliser les processeurs supplémentaires pour travailler sur un nombre de données plus grand ? Prenons l'exemple d'une application de traitement d'image : au lieu de travailler sur une image de 2048*2048. plus simple est encore de diminuer le temps d’exécution du code série en optimisant le code. cela correspond à une quadri-core incorporant la technologie SMT comme on en trouve chez intel.siteduzero. En clair : au-delà d'un certain nombre de processeurs. augmenter le nombre de processeurs ne sert pas vraiment à grand chose : doubler leur nombre revient souvent à augmenter les performances d'un misérable pourcent.11% Si notre programme ne peut pas voir P augmenter. aussi bien logiciels que matériels qui limitent les performances des programmes conçus pour exploiter plusieurs processeurs. et on regarde ce qui se passe en augmentant le nombre de processeurs. un code passant 50% de son temps à exécuter du code parallèle ne sera pas vraiment exécuté plus vite. Elle ne tient pas compte des nombreux problèmes techniques. au-delà de 8 processeurs. une dernière remarque : la loi d'Amdhal est optimiste : elle a été démontrée en postulant que le code parallèle peut être réparti sur autant de processeurs qu'on veut et peut profiter d'un grand nombre de processeurs. qui permettrait d'utiliser plusieurs processeurs différemment ? Et bien si ! Avec la loi d'Amdhal. Cette solution devient donc très limitée quand on augmente le nombre de processeurs. histoire de rentabiliser ? www. 4 . il vaudra mieux diminuer le temps d’exécution de celui-ci plutôt que de chercher à rajouter beaucoup de processeurs inutilement. on a pris un programme qui travaille toujours sur des données de même taille et on n'a pas augmenté le nombre de données à traiter avec le passage de 1 à N processeurs. Dans la réalité. C'est la seule solution viable pour un programme contenant peu de code parallélisable. rares sont les programmes de ce genre : certains programmes peuvent à la rigueur exploiter efficacement 2. sans forcément chercher à le paralléliser. Dans la réalité Enfin.com .

Dans ce cas. qu'on note On trouve la loi de Gustafson qui nous donne le gain en fonction du nombre de processeurs : En remarquant que .com . mais chacun de ces processeurs va travailler sur une donnée différente.siteduzero. Une seule donnée Avec une seule donnée. durant le temps . le temps vaut donc . on trouve alors la loi de Gustafson www. On pourra prendre N processeurs et exécuter sur chacun d'eux le code paralléle sur une des N données. le temps d’exécution du code paralléle. pour N données. En prenant 1 ou N processeurs. la partie série restera la même et sera executée sur un seul processeur durant un temps .Partie 1 : Le parallélisme : un peu de théorie 17/97 Cela s'appelle le parallélisme de données . données unes par une. Et là encore. une petite remarque s'impose : est égal au pourcentage de temps . passé à exécuter le code série et celui du code paralléle (sur N processeurs). Ce Si ce calcul fait sur ces données avait été fait sur un seul processeur. on peut trouver une loi similaire à la loi d'Amdhal. fichier son. qui consiste à exécuter le même programme sur des données différentes et indépendantes. Celui-ci prend un temps à exécuter son code série et un temps pour exécuter son code parallèle. est égal à . Cela permet donc de traiter N données sur N processeurs en même temps. Ce code parallèle sera exécuté simultanément sur plusieurs données : ainsi. on aurait dû calculer ces qui aurait pris un temps égal à Gain En calcule donc le gain Cette équation se factorise en A ce stade. on peut demander à notre processeur d’exécuter un programme sur données. ce . mais beaucoup moins restrictive : la loi de Gustafson ! Hypothèses de base Prenons un programme s’exécutant sur un seul processeur. avec le temps d’exécution du code série et celui Plusieurs données Par contre. Ce code parallèle s’exécute sur une donnée (image. ou autre). on pourra demander à chacun des processeurs de traiter une donnée en un temps qui fait qu'en un temps . Pour résumer : tous les processeurs exécutent un seul et unique programme ou une suite d'instructions.

N'oublions pas que ces formules sont théoriques. cela prendrait un temps égal à Mais surtout. V oici donc quelques conclusions que l'on peut tirer avec ce que l'on vient de voir. du langage de programmation utilisé et de la manière dont a été programmé le programme en question. Ce parallélisme est en effet soumi à la loi d'Amdhal. et donc au gain que l'on peut obtenir. alors on ne gagne rien en terme de temps de calcul. En effet. le parallélisme de données. celles-ci sont toutes traitées par un processeur simultanément et le temps mis pour traiter données sur processeurs sera identique au temps mit pour traiter une donnée sur un seul processeur. plus ce genre de parallélisme est efficace. Paralléliser un programme qui exécute de nombreux calculs en parallèle sur le même ensemble de données est voué à montrer "rapidement ses limites". il faut se rappeler que la loi de Gustafson s'applique sur une durée déterminée : elle ne rend pas les calculs plus rapides : si une donnée met un temps à être traitée. il n'y a pas de limites théoriques à : on peut mettre autant de données que l'on veut. Par contre. Bien sûr. On peut citer par exemple la métrique de Karp–Flatt. www. Plus le nombre de données traitées en parallèles est grand. Il existe néanmoins d'autres formules ou lois permettant de déduire plus ou moins précisément l'efficacité théorique de la parallélisation d'un programme quelconque. consistant à effectuer un même programme/sousprogramme sur un ensemble de données différentes donne de très bons résultats. si on augmente le nombre de données. Mais dans la réalité.com . sur un seul processeur. Cela correspond au parallélisme de données : exécuter simultanément un même programme sur des données indépendantes. qui dépendent de l'architecture des processeurs utilisés. et ne servent qu'a donner des indictions qualitatives. aucune de ces formules n'est utilisable directement : de nombreux autres paramètres interviennent. avec processeurs. Aucune limite n'existe concernant la quantité de données traitables simultanément.Partie 1 : Le parallélisme : un peu de théorie 18/97 Que nous apprend Gustafson ? La loi de Gusatfson répond à un besoin certain : comment faire pour exploiter au mieux le parallélisme lorsque l'on souhaite travailler sur une grande quantité de données. et que ces données doivent être traitées par la partie parallélisée du programme.siteduzero.

il faut utiliser des processeurs spéciaux. le fait qu'une instruction doive attendre le résultat d'une autre peut rendre l'unité de calcul inutilisée durant un moment. Autant dire que niveau efficacité. Par quelques cycles. on trouve un petit circuit qui est capable d'effectuer des calculs. dans certains cas. il a bien fallu adapter le matériel aux différentes formes de parallélisme. quelqu'un a eu une idée fabuleuse : puisqu'on arrive pas à trouver de quoi donner du travail à notre unité de calcul avec un seul programme. nous allons maintenant nous intéresser au fonctionnement des ordinateurs à plusieurs processeurs et des architectures multicœurs.com . Mine de rien. et autre techniques aux noms qui font peur aux enfants sont de la partie. Multicoeurs et Hyperthreading Après avoir vu l'organisation globale des architectures parallèles et les fameuses loi d'Amdhal et de Gustafson. Pour cela. Celui-ci s'appelle l'unité de calcul . Out Of Order . on a vu des ordinateurs possédant plusieurs processeurs.siteduzero. un paquet de phénomènes bizarres aux noms à coucher dehors (dépendances de données. mais c'est celui qui va nous intéresser dans ce chapitre. Par exemple. Mais il arrive toujours que notre unité de calcul soit inutilisée durant quelques cycles. Après tout. et pour s'en rendre compte. Hyperthreading et compagnie Dans ce qui précède. De nombreuses techniques ont étés inventées pour faire en sorte que notre unité de calcul soit utilisée au plus possible : utilisation de caches non-bloquants. mais sachez que c'est faux ! Il est parfois possible pour un processeur seul d’exécuter plusieurs programmes en même temps. exécution superscalaire. forwarding. on pourrait prendre des instructions en provenance de plusieurs programmes et www. Le pipeline fait des bulles ! Il arrive souvent que l'unité de calcul d'un processeur ne fasse rien. on veut dire que quand on accède à la mémoire RAM. on peut mieux faire ! Un jour. accès à la mémoire.Partie 2 : Multi-processeurs. Multicoeurs et Hyperthreading 19/97 Partie 2 : Multi-processeurs. on peut faire patienter l'unité de calcul durant une bonne centaine de cycles d'horloge. loin de là. rien de tel qu'une petite virée au centre de nos processeurs actuels. mauvaise prédiction de branchement. qui utilisent des ruses de sioux. Ce circuit n'est pas le seul présent dans notre processeur. etc) peuvent aboutir à ce genre de choses. pourquoi ne pas essayer avec plusieurs programmes ? Au lieu de faire en sorte de ne réserver notre unité de calcul qu'aux instructions d'un seul programme. Du parallèlisme avec un seul processeur A l'intérieur d'un processeur. V ous devez donc fatalement penser qu'il faut obligatoirement plusieurs processeurs pour éxecuter plusieurs programmes en parallèle.

Suivant la méthode utilisée. C'est le processeur qui se charge de passer automatiquement d'un programme à un autre. on va détailler ces différentes façons.com . elle sera disponible pour un autre. Fine Grained Multithreading La première méthode consiste à switcher entre les programmes : on exécute une instruction d'un programme.Partie 2 : Multi-processeurs. puis on passe au second programme. on peut se retrouver avec des gains plus ou moins intéressants et une unité de calcul plus ou moins utilisée.siteduzero. Dans ce qui va suivre. www. Multicoeurs et Hyperthreading 20/97 les répartir sur notre unité de calcul suivant les besoins : si un programme n'utilise pas notre ALU. et ainsi de suite. sans temps d'attente. Types de super-threading matériel explicite Il existe différentes manières pour remplir notre unité de calcul avec des instructions en provenance de plusieurs programmes.

Ces deux instructions n'appartenant pas au même programme.siteduzero. Multicoeurs et Hyperthreading 21/97 Cette technique a de nombreux avantages : on est certain que lorsque une instruction s’exécute. Mais ces processeurs ont un défaut : pour obtenir de bonnes performances. on est obligé d'avoir beaucoup de programmes en cours d’exécution. diverses raisons techniques vont faire que l'unité de calcul sera inutilisée durant pas mal de temps.com . et l'unité de calcul est mieux utilisée.Partie 2 : Multi-processeurs. il est impossible qu'une instruction doive manipuler le résultat d'une autre. Cela permet de grandement simplifier le processeur vu que l'on a pas besoin de gérer le cas où une instruction doit attendre le résultat d'une autre : ça fait des circuits en moins. Sans cela. elle n'a pas à attendre le résultat de l'instruction exécutée juste avant elle. www.

Évidemment. www.Partie 2 : Multi-processeurs. 3. le processeur peut décider si besoin d'alimenter l'unité de calcul avec ces instruction du même programme. cela nécessite des conditions particulières. Cette technique s'appelle la dependance lookahead technique. au lieu de changer de programme constamment. certains processeurs peuvent décider de lancer plusieurs instructions d'un même programme à la suite sur l'unité de calcul.siteduzero. Ainsi. Multicoeurs et Hyperthreading 22/97 Pour ceux qui ont lu mon tutoriel fonctionnement d'un ordinateur depuis zéro. etc : instructions à la suite sans problème. 2. je peux aller plus loin dans les précisions : il faut autant des programmes en cours d’exécution que d'étages dans notre pipeline Pour limiter la casse. Généralement.com . chaque instruction va contenir quelques bits qui permettront de dire au processeur : tu peux lancer 1. il n'y aura pas de dépendances entre ces instructions.

Coarse Grained Multitrheading Autre forme de parallélisme matériel : le Coarse Grained Multitrheading . et les techniques de dependance lookahead n'arrivent pas forcément à limiter la casse. www. Avec celui-ci. Multicoeurs et Hyperthreading 23/97 La technique du Fine Grained Multithreading pose tout de même quelques problèmes : on n'a rarement suffisamment de programmes pour alimenter l'unité de calcul convenablement. le processeur décide toujours de changer de programme.com . mais il fait nettement moins souvent. Aussi cette technique de parallélisme matériel a assez peu d'efficacité en dehors de quelques cas particuliers. Le changement de programme se fait lors de certains évènements particuliers qui prennent du temps.siteduzero.Partie 2 : Multi-processeurs.

Partie 2 : Multi-processeurs. ça la fout mal ! Simultaneous Multithreading Les techniques vues au-dessus imposent pas mal de contraintes et surtout peuvent laisser l'unité de calcul inutilisée durant assez longtemps. branchements. nettement plus intéressant : certains processeur décident tout seuls quand changer de programme ! Généralement. Et enfin. ces processeurs changent de programme à éxecuter lorsqu'on doit accéder à la mémoire (lors d'un cache miss). autant changer de programme pour remplir les vides avec des instructions d'un autre programme. aussi changer de programme et éxecuter des instructions pour recouvrir l'accès à la mémoire est une bonne chose. à une différence prêt : aucun programme n'a la priorité sur l'autre. Pas besoin de changer de programme tout les cycles ou lors d'un évènement particulier : les deux programmes s’exécutent en même temps. on utilise une instruction de changement de programme. les évènements faisant changer de programme ne sont pas forcément les mêmes. branchement mal prédit.siteduzero. Il faut dire que l'accès à la mémoire est quelque chose de très lent. Au lieu de passer tout ce temps à ne rien faire. et chaque programme va utiliser l'unité de calcul dès que les autres la laissent libre. jusqu'à ce qu'un évènement rendant l'unité de calcul inutle durant un certain temps arrive : accès à la mémoire (cache miss). Sur certains processeurs. En termes techniques. etc. Il s'agit du Simultaneous Multi-Threading ou SMT. Sur certains processeurs. www. Suivant le processeur. Multicoeurs et Hyperthreading 24/97 L'idée est d’exécuter les instructions d'un programme normalement. on change de programme lorsque certaines instructions sont exécutées : accès à la mémoire. Il faut tout de même remarquer une chose : pour être efficace. fournie par le jeu d'instruction du processeur : elle seule peut faire changer le programme exécuté.com . ce genre de processeurs a besoin de pouvoir accéder à la mémoire cache pendant qu'il attend des données en provenance de la mémoire. Cette technique consiste à faire pareil que ses prédécesseurs. il n'est pas rare que les instructions du programme fraichement démarré doivent accéder à la mémoire cache pour faire leurs calculs : autant dire que si le cache n'est pas utilisable parce qu'un autre programme est interrompu pour accéder à la mémoire. il existe une dernière technique qui permet de mieux exploiter l'unité de calcul dans pas mal de situations. etc. on dit que celui-ci a besoin de caches non-bloquants. Et oui. Pour éviter ce genre de choses.

Ainsi. au lieu d'utiliser une seule unité de calcul. Multicoeurs et Hyperthreading 25/97 Ce fameux Simultaneous Multi-Threading porte un autre nom : Intel lui a en effet donné le nom d'Hyperthreading . voire encore plus unités de calcul. Et il faut savoir que les techniques vues au-dessus se marient très bien avec la présence de plusieurs unités de calcul. ceux-ci peuvent répartir les instructions d'un programme sur ces différentes unités de calcul histoire d'aller plus vite. mais plusieurs unités de calcul.com . C'est ainsi. Le premier processeur Intel qui a intégré cette technologie était le processeur Intel Pentium 4. Hyperthreading est quand même un nom un peu plus vendeur que Simultaneous Multi-Threading . trop long) qui faisaient qu'il utilisait assez peu son unité de calcul et qu'il avait énormément de vides à remplir. On a beaucoup plus d'opportunités pour éxecuter des instructions de programmes différents ! www. pour de sombres raisons marketing . sans problèmes (ou presque).Partie 2 : Multi-processeurs. L'Hyperthreading était limité à deux programmes/ threads logiciels différents et ne pouvait faire plus. 4. Après tout. 8.siteduzero. Processeurs superscalaires Nos ordinateurs modernes implémentent souvent non pas une. Il faut dire que ce processeur possédait des caractéristiques techniques (un pipeline long. nos processeurs peuvent contenir entre 2.

www. 5464 Instruction Charger le contenu de l'adresse 0F05 Charger le contenu de l'adresse 0555 Additionner ces deux nombres Charger le contenu de l'adresse 0555 Faire en XOR avec le résultat antérieur .. ce registre permet de localiser la prochaine instruction à éxecuter : il suffit d'augmenter le contenu de ce registre de façon à le faire pointer sur l'instruction suivante à chaque exécution d'une instruction. stockée dans la mémoire RAM de notre ordinateur à un endroit bien précis.. Multicoeurs et Hyperthreading 26/97 Et dans le processeur ? Reste à savoir comment notre processeur est capable de réussir ce genre de prouesses. Celles-ci sont placées les unes à la suite des autres dans l'ordre où elles doivent être exécutées. ce n'est pas si difficile que ça ! Mais pour commencer. quelques rappels sont de rigueur ! Cycle Fetch / Exécution Nos processeurs sont censés éxecuter des instructions machines. le processeur contient une petite mémoire qui contient la position (l'adresse mémoire) de notre instruction dans la RAM de l'ordinateur. Ce sont les instructions de branchements . Il existe quand même un moyen pour modifier l'ordre d’exécution des instructions : certaines instructions permettent de modifier le contenu du Program Counter. Vu que nos instructions sont placées les unes après les autres en mémoire.. Mais notre unité de calcul n'est pas le seul circuit nécessaire pour éxecuter notre instruction : notre instruction n'arrive pas de nulle part ! Chaque instruction est représentée dans notre ordinateur sous la forme d'une suite de bits.Partie 2 : Multi-processeurs..siteduzero. Ce fameux registre s'appelle le Program Counter. Par exemple : Adresse 0 1 2 3 4 . ceux-ci comportent donc une unité de calcul.com . Et on va le voir. Ces instructions permettent de "sauter" directement à une instruction voulue dans le programme et poursuivre l'exécution à partir de celle-ci. Pour ce faire. qui est capable d’exécuter notre instruction. Instruction d'arrêt Pour se souvenir d'où il en est.

En clair. Niveau circuits Autrefois. Ce chargement est toutefois un peu plus compliqué : un processeur ne fonctionne pas aussi simplement. Pour information. Cela a un avantage certain : l'unité de chargement (aussi appelée l'unité de Fetch ) peut ainsi éxecuter une instruction sur l'unité de calcul et commencer à charger l'instruction suivante. et notre circuit chargé de récupérer une instruction depuis la mémoire est un peu plus complexe : il est découpé en plusieurs circuits qui effectuent des taches diverses comme " décoder l'instruction " ou " renommer des registres ". chacune avec son Program Counter. ce n'est pas le cas : ces deux circuits sont séparés. Notre processeur contient aussi de petites mémoires ultra-rapides qui servent à stocker temporairement des données : on les appelle des registres .siteduzero. pour plus de simplicité et de performances. le Program Counter est un de ces registres. www. on va devoir trouver un moyen pour charger plusieurs instructions. ceux-ci doivent charger à l'avance plusieurs instructions : au minimum une par programme ! Il restera ensuite à les répartir sur notre unité de calcul suivant les besoins. Pour cela. il va falloir la charger depuis la mémoire. Multiple Fetch Les processeurs permettant d’exécuter plusieurs programmes utilisent cette technique à fond. En effet. Mine de rien. et l'unité de calcul. on fusionnait les circuits chargés du chargement de notre instruction (et de tas d'autres taches annexes). ceux-ci doivent remplir les vides de notre unité de calcul avec des instructions en provenance de plusieurs programmes.com . Généralement. et un circuit qui se charge de récupérer l'instruction depuis la mémoire. Mais aujourd’hui. Pour identifier chacun des registres.Partie 2 : Multi-processeurs. Mais cela ne nous intéresse pas ici. qui contient notamment notre fameux Program Counter. V oilà qui est simple. Cela consiste à interpreter la suite de bits qui représente l'instruction et en déduire comment configurer l’unité de calcul et les autres circuits du processeur. la solution est assez simple : il suffit d'utiliser plusieurs unités de chargement. Un programme pourra manipuler le contenu des registres. C'est le rôle d'un ou de plusieurs circuits présents dans notre processeur. Multicoeurs et Hyperthreading 27/97 Cette instruction. en provenance de programmes différents. une instruction qui doit manipuler une donnée contenue dans un registre devra indiquer qu'elle veut manipuler ce registre dans sa suite de bits. On peut donc considérer que notre processeur est découpé en deux grands circuits : une unité de calcul (aussi appelée ALU) qui se charge de faire les calculs. qui ne sont rien d'autres que des numéros. et l'amener de la mémoire à notre unité de calcul. on donne à ceux-ci des noms.

le dispatcher se chargera de répartir les différentes instructions sur les unités de calcul. Quoiqu'il en soit. www. on peut répartir nos instructions en provenance de divers programmes sur notre ALU. Il y en a un par programme. cela n'est pas la seule solution : on peut très bien rassembler ces plusieurs unités de chargement en une seule grosse unité. avant d'être réparties sur notre ALU au besoin. Et pour cela. Pour cela. Decoupled architectures Il faut bien stocker ces instructions préchargées quelque part. on trouve non pas un. Ainsi.siteduzero. Cette petite mémoire s'appelle l'instruction buffer . avec plusieurs Program Counter (toujours un par programme). et laisser le dispatcher faire son travail.com . mais plusieurs Instruction Buffers. on va intercaler une petite mémoire entre l'unité de chargement/décodage et l'unité de calcul. Mais cela est un détail.Partie 2 : Multi-processeurs. Multicoeurs et Hyperthreading 28/97 Toutefois. un circuit spécialisé. Sur les processeurs utilisant le SMT. Il suffit de précharger les instructions en provenance de nos programmes dans cet Instruction Buffer. Nos instructions seront donc mises en attente dans cette mémoire tampon. beaucoup de circuits sont dupliqués dans un processeur utilisant le SMT.

Et voilà. et chacun d'entre eux se verra attribuer à un programme différent. Cela évite que nos processeurs se marchent sur les pieds. Il faudra donc dupliquer les registres histoire que chaque programme puisse avoir son ensemble de registres rien qu'à lui. Multicoeurs et Hyperthreading 29/97 Jeu de registres Il nous reste enfin un léger petit problème à régler : chaque programme manipule des registres et il semble difficile de partager un registre pour le laisser stocker plusieurs données en provenance de plusieurs programmes. cela est fait en dupliquant les registres. www. qui utilise du SMT.com . Pour éviter les ennuis. et aussi en ajoutant des circuits qui permettront de savoir à quel programme est attribué chaque registre. dupliquer des registres ne suffira pas. on va donc décider de placer chaque registre ayant un nom en plusieurs exemplaires : plusieurs registres auront le même nom. Il faudra aussi faire quelques modifications : les registres étant identifiés par des noms. on obtient enfin notre processeur. ils utiliseront le même registre. Si deux programmes utilisent le même nom de registre.Partie 2 : Multi-processeurs.siteduzero. Dans les faits.

Multicoeurs et Hyperthreading 30/97 www.siteduzero.com .Partie 2 : Multi-processeurs.

V ous connaissez surement ce fameux processeur. Puis. Ainsi. notre processeur CELL peut être vu comme intégrant un cœur principal POWER PC version 5. vous avez la chance d'en posséder un. par exemple. Les premières tentatives pour implémenter le parallélisme au niveau matériel ont étés des plus simples : il suffisait simplement de mettre plusieurs ordinateurs ensemble et de les relier via un réseau local. on a ensuite utilisé une autre méthode : placer plusieurs processeurs dans la même machine. Évidemment. Il faut chercher dans votre console de jeux : et oui. dans lequel on place des processeurs identiques sur la même puce de silicium. il ne faut pas chercher dans l'unité centrale de votre PC de bureau. Multicœurs asymétrique Dans la grosse majorité des cas.com . on a vu que le parallélisme de tache pouvait s’accommoder d'un seul processeur. On peut très bien utiliser un cœur principal avec des cœurs plus spécialisés autour. etc. qu'on retrouvait autrefois dans les Mac. quadcore (4 cœurs).siteduzero. votre PS3 contient un processeur CELL. certains ordinateurs contenaient une carté mère sur laquelle on pouvait mettre plusieurs processeurs. Cela s'appelle du multicœurs asymétrique. un processeur multicœurs n'est rien d'autre qu'une puce de silicium sur laquelle on a placé plusieurs processeurs ! Et oui. Multicoeurs et Hyperthreading 31/97 Processeurs multicoeurs Dans les chapitres précédents. mais cela ne vous a pas vraiment éclairé ? Et bien je vous le donne en mille : en fait. Mais ce n'est certainement pas une obligation : on peut très bien mettre plusieurs processeurs assez différents sur la même puce. il pourrait paraitre bizarre qu'un seul processeur puisse éxecuter plusieurs programmes simultanément. sans que cela ne pose problème. et rien ne vaut l'utilisation de plusieurs processeurs pour réellement tirer partie du parallélisme de taches. non. Logiquement. Et ce n'est pas un hasard : pour créer ce genre de processeurs. celui-ci sera appelé un processeur dual-core (deux cœurs). octo-core (8 cœurs). c'est quoi ? Avant toute chose. et attendre que le besoin s'en fasse sentir. Ce terme est à opposer au multicœurs symétrique. généralement 2 ou 4. chacun dans son coin. Le multicœurs. ce n'est rien de plus que cela. et environ 8 processeurs auxiliaires. Ces processeurs permettent aussi d'éxecuter plusieurs programmes à la fois.Partie 2 : Multi-processeurs. Mais sachez que ce genre d'intuitions est toujours trompeuse ! Intégrer deux cœurs sur une même puce a au contraire de nombreux avantages en terme de performances. On vous a surement dit que ce processeur contenait plusieurs cœurs. ces techniques ne sont pas des plus efficaces. Néanmoins. On pourrait croire que placer deux processeurs sur la même puce est un peu du gâchis : pourquoi ne pas simplement utiliser deux processeurs séparés. www. et vous en possédez peut-être un chez vous. Suivant le nombre de cœurs présents dans notre processeur. les cœurs d'un processeur multicœurs sont tous identiques. Chacun de ces processeurs intégré dans ce circuit s’appellera un coeur. il a fallu faire de nombreux progrès dans les technologies de fabrications de nos processeurs. un processeur n'est censé éxecuter qu'un programme à la fois (sauf si celui-ci utilise le Simultaneous Multithreading ). Processeurs multicœurs V ous avez tous surement entendus parler des processeurs multicœurs. capables d’exécuter des programmes en parallèle. Mais utiliser plusieurs processeurs n'est pas la seule solution pour éxecuter plusieurs programmes en parallèles. et si ça se trouve. Pour simplifier. L'architecture CELL Le processeur CELL est un des exemples les plus récent de processeur multicœurs asymétrique. Ces processeurs multicœurs sont apparus ces dernières années.

l'organisation des caches et des différentes mémoires intégrées au processeur CELL est assez déroutante. Ceux-ci sont basés sur l'architecture Bulldozer. et les autres bus entre eux. ce n'est pas vraiment le cas : certains circuits sont communs et partagés entre plusieurs cœurs. Pour cela. mais ce n'est pas tout à fait vrai non plus : certaines architectures outrepassent légèrement cette règle. sont reliés via la mémoire et aux différents bus de notre ordinateur pas un bus intercalé. Mais sur certains processeurs multicœurs. la mémoire. Et c'est au programmeur de gérer tout ça ! C'est le processeur principal qui va envoyer aux SPE les programmes qu'ils doivent éxecuter. on avait réellement plusieurs cœurs séparés. Cette technique consistant à ne pas dupliquer certains circuits et à en partager certains s'appelle le cluster multithreading . Ceux-ci sont capables de faire transiter des blocs de mémoire entre la mémoire centrale et le local store du SPE.Partie 2 : Multi-processeurs. chacun avec se propres circuits rien qu'à lui. le processeur principal est appelé le PPE. tout deux spécialement dédié à celui-ci. Ce n'est pas tout à fait faux. le Local Store . notre processeur principal va simplement écrire dans le local store du SPE. il faut savoir que les SPE possèdent des instructions permettant de commander leur contrôleur DMA et que c'est le seul moyen qu'ils ont pour récupérer des informations depuis la mémoire. et sans devoir passer par un intermédiaire. Cette technique est notamment utilisée sur les processeurs FX-8150 et FX-8120 d'AMD. Avec ces processeurs. C'est un peu l'exact opposé de ce qui se passe pour le processeur principal : celui-ci peut aller trifouiller la mémoire RAM de l'ordinateur sans problème. On remarque que nos processeurs. qui communique avec le processeur principal via un bus spécial. Sur les processeurs précédents. PPE et SPE. Pour cela. Notre processeur principal intégré un cache L1. On peut considérer que notre processeur principal va déléguer des calculs à effectuer aux SPE. Pour rentrer dans les détails scabreux. www. ces processeurs doivent passer par un intermédiaire : des contrôleurs DMA. cette mémoire fait dans les 256 kibioctets : c'est très peu. il leur est interdit d'aller manipuler la mémoire centrale de l'ordinateur directement. Cluster Multithreding Plus haut. j'avais dit qu'un cœur n'était rien d'autre qu'un processeur. tous les cœurs se partagent l'unité de calcul sur les nombres flottants (les nombres à virgule).com . et va lui envoyer une demande lui ordonnant de commencer l’exécution du programme qu'il vient d'écrire.siteduzero. ainsi qu'un cache L2. Nos SPE sont reliés à une mémoire locale. Chaque SPE peut ainsi aller charger ou enregistrer des données dans cette mémoire locale. qui relier tous les processeurs. Multicoeurs et Hyperthreading 32/97 Sur le schéma du dessus. et quelques autres processeurs de la même gamme. Comme on le voit sur ce schéma. Par contre. Pour information. et les processeur auxiliaires sont les SPE. mais suffisant pour ce que ces processeurs doivent faire.

siteduzero. Le seul problèmes.Partie 2 : Multi-processeurs. Il est en effet évident qu'un seul circuit partagé entre tous les cœurs prendra moins de place et utilisera moins de composants élèctroniques que plusieurs circuits (un par cœur). Le partage des caches C'est bien beau de mettre plusieurs processeurs sur une même puce. Mais c'est un détail : en choisissant bien les circuits à partager. qui peuvent tous les deux accéder à une seule et unique mémoire cache. la mémoire cache est partagée entre les cœurs. chaque cœur possède sa propre mémoire cache rien que pour lui. Sur d'autres microprocesseurs multicœurs. encore une fois. nous allons parler de ce bon vieux cache. dans le cas le plus simple. sur certains processeurs. Multicoeurs et Hyperthreading 33/97 Ce partage des circuits a une utilité : cela permet d'éviter de dupliquer trop de circuits. cela peut être assez indolore. Caches dédiés versus caches partagés Tout d'abord. l'organisation des différents caches d'un processeur multicœurs est légèrement plus complexe que prévu. mais il ne faut pas oublier ce qui arrive à LA pièce maitresse de tout processeur actuel : sa mémoire cache ! Et oui. c'est que ce partage peut parfois se faire avec des pertes de performances.com . Parce que mine de rien. www.

voire 60% des circuits de notre processeur servent à intégrer la mémoire cache ! Si vous ne me croyez pas. Multicoeurs et Hyperthreading 34/97 Ces deux méthodes ont des inconvénients et des avantages. Mais avant toute chose. il faut savoir que la quantité de mémoire cache que l'on peut placer sur une surface donnée est limitée : on ne peut pas mettre autant de cache que l'on veut dans un processeur. voici un exemple avec un processeur dual-core Inrel core 2 duo. celui-ci n'aura accès www. Si on exécute un programme prévu pour n'utiliser qu'un seul cœur.siteduzero.com . J'ai entouré le cache en rouge. ou un unique cache aussi gros que la somme de tous les caches séparés ? Le principal défaut des architectures à cache dédiés vient de là. Et cela pose un problème pour les architectures qui utilisent des caches séparés pour chaque cœur : ceux-ci seront individuellement assez petits. Alors qu'est-ce qui est le mieux : pleins de caches plus petits. Autant vous dire que le cache est une ressource précieuse. Et le cache prend une très grande place dans notre processeur : environ la moitié.Partie 2 : Multi-processeurs.

dans laquelle certains caches sont partagés et pas d'autres. Généralement. Par contre. c'était nul comme blague. comme le L2 ou le L3 sans trop pourrir leur temps d'accès. La vraie raison tient dans le fait que ce cache doit avoir une latence très faible. les architectures à cache partagé ont aussi leurs problèmes : plusieurs programmes utilisent le même cache et peuvent se marcher sur les pieds. on peut alors partager le cache de façon à ce que chacun aie sa juste part. Partager le cache L1 serait parfaitement possible. Sur un processeur à cache séparé. Rien n’empêche à un programme d'utiliser une portion du cache déjà utilisée par un autre : l'autre programme devant alors recharger la donnée stockée dedans en mémoire. la mise à jour des autre version de la donnée dans le cache des autres processeurs se fera en passant par la mémoire RAM comme intermédiaire. Avec des caches dédiés. on se doute que les performances seront catastrophiques ! Avec les processeurs à cache partagé. Multicoeurs et Hyperthreading qu'à un seul cache : celui dédié au cœur sur lequel il va s’exécuter.pourquoi le cache L1 a droit à ce genre de traitement de faveur ? Très simple : il est trop moche pour mériter d'être partagé.Partie 2 : Multi-processeurs. Avec un cache partagé. www. Le L2 et le L3 sont souvent partagés. Vu la lenteur de la mémoire RAM. et que partager un cache n'est jamais vraiment innocent en terme de temps d'accès. il y a juste à mettre à jour la donnée dans le cache unique. Qui plus est. le L2. rien n’empêche de partager les autres caches. et on se retrouve avec une organisation assez hybride. ni rien d'autre. et puis c'est tout : pas de mise à jour des données dans les caches des autres cœurs. il faut nuancer un tout petit peu les choses : un processeur multicœurs ne contient pas qu'un seul cache. et le L3. mais rendrait celui-ci tellement lent qu'on aurait l'impression que nos programmes tourneraient au ralenti.. le programme gourmand pouvant utiliser autant de mémoire cache qu'il le peut. Mais ce défaut est assez rare en pratique. et on obtient souvent de meilleurs performances avec un cache partagé qu'avec des caches dédiés. Par contre. si une donnée présente dans plusieurs caches est modifiée. Oui. j'avoue. on trouve deux à trois caches dans un processeur (multicœurs ou non) : le L1. 35/97 Et cela peut aussi se généraliser avec plusieurs programmes : si un programme a besoin de beaucoup de cache et l'autre très peu. les processeurs à caches partagé ont un gros avantage. le cache utilisable par ce programme aurait été plus petit.. si une donnée est utilisée par deux cœurs et est présente dans le cache. il aura accès à une mémoire cache unique beaucoup plus grosse : on a moins de cache miss.siteduzero. et le programme aurait pu manquer de mémoire cache. tandis que le L1 n'est jamais partagé ! Heu.com . et donc des performances qui s'envolent (vraiment !). La réalité Dans la réalité.

c'est quoi une interruption ? C'est une fonctionnalité de notre processeur qui va permettre d’arrêter temporairement l’exécution d'un programme pour en exécuter un autre. reprend l'exécution du programme suspendu là ou il en était. avant de rendre la main au programme interrompu. Ce petit traitement est fait par un petit programme au auquel on a donné un nom technique : routine d'interruption. rien n’empêche pour un processeur quad-core d'avoir deux caches L2. Notre interruption va donc devoir effectuer un petit traitement (ici. Communication inter-processeurs Bon. Multicoeurs et Hyperthreading 36/97 On peut décider de partager un cache entre tous les cœurs. Holà. Pour expliquer comment notre système d'exploitation se débrouille pour lancer un programme sur un autre processeur ou un autre coeur. cela est géré en partie par le système d'exploitation de votre ordinateur. mine de rien.Partie 2 : Multi-processeurs.) et de la traiter en temps voulu. c'est bien beau d'avoir plusieurs processeurs ou plusieurs coeurs.. exécute la routine d'interruption. Lorsqu'un processeur doit exécuter une interruption. voire limiter ce partage à quelques cœurs particuliers pour des raisons de performances. www.siteduzero. mais comment on fait pour les utiliser ? Et oui. demande faite par l'OS ou un programme. il faut bien trouver comment assigner un programme à un processeur ou un cœur en particulier ! Je vous dit tout de suite. et le cache L3 partagé entre tous les cœurs. erreur fatale d’exécution d'un programme. il va falloir expliquer une notion : celle d'interruption. lancer un programme sur un processeur).com . Ces interruptions ont pour but d'interrompre l’exécution d'un programme afin de réagir à un événement extérieur (matériel. celui-ci : arrête l'exécution du programme en cours d'exécution. Ainsi. chacun partagés avec deux cœurs. même si le matériel a son mot à dire..

au besoin. un programme système exécutant des interruptions est fourni par votre OS. des contrôleurs APIC. les carte mères multiprocesseurs devaient incorporer un contrôleur spécial en complément. Celui-ci a été remplacé par plusieurs autres contrôleurs. C'est donc le système d'exploitation qui fournira cette routine. qui s'occupe de gérer les interruptions en provenance des périphériques et de les redistribuer vers les local APIC . notre processeur ou notre carte mère incorpore un circuit qui s'occupe de gérer les interruptions déclenchées par le matériel et qu'on appelle le contrôleur d'interruptions . la gestion des programmes à exécuter en parallèle se fera à grand coup d'interruptions inter-processeurs . ces interruptions seront déclenchées par le programme parallélisé : celui-ci déclenchera une interruption pour démarrer un thread . ce circuit doit être adapté pour pouvoir rediriger des interruptions déclenchées par un processeur vers un autre. chaque processeur possède un local APIC . Dans notre cas. Pour démarrer un programme sur un autre processeur. il suffit juste d'écrire une routine qui soit programmée pour. il suffira d'envoyer une interruption vers cet autre processeur afin de le réveiller et faire en sorte que notre interruption initialise celui-ci correctement pour lancer le programme voulu. qui s'occupe de gérer les interruptions en provenance ou arrivant vers ce processeur. Dans notre cas. Ce IO-APIC s'occupe aussi de gérer les interruptions inter-processeurs en faisant passer les interruptions d'un local APIC vers un autre.com . Et pour cela. On trouve aussi un IO-APIC . Pour gérer cette situation. Par exemple. plus évolués et capables de gérer les architectures multiprocesseur et multicœurs. V oici comment nos programmes applicatifs peuvent exploiter le matériel sans se fatiguer : en exécutant l'interruption qui va bien. nos anciens PC incorporaient sur leur carté mère un contrôleur d'interruption crée par Intel qui se nomme le 8259A. Pour générer des interruptions inter-processeur. Multicoeurs et Hyperthreading 37/97 Notre OS et nos pilotes fournissent toutes les routines d'interruptions de bases pour que notre matériel fonctionne : la majorité des programmes systèmes sont des interruptions. Pour simplifier. V ous voulez écrire une donnée sur le disque dur. Ces interruptions inter-processeurs ne sont rien d'autre que des interruptions déclenchées par un processeur ou un cœur et envoyées vers un autre. Mais celui-ci ne gèrait pas les interruptions inter-processeurs.siteduzero. L'exemple du x86 Généralement.Partie 2 : Multi-processeurs. www.

Partie 2 : Multi-processeurs, Multicoeurs et Hyperthreading

38/97

Tous les local APIC et l'IO-APIC sont reliés ensembles par un bus APIC spécialisé, par lequel ils vont pouvoir communiquer et s'échanger des demandes d'interruptions. On peut préciser quel est le processeur de destination en configurant certains registres du IO-APIC, afin que celui-ci redirige la demande d'interruption d'un local APIC vers celui sélectionné. En gros, cela se fait avec l'aide d'un registre de 64 bits nommé l'Interrupt Command Register . Pour simplifier le mécanisme complet, chaque processeur se voit attribuer un Id au démarrage qui permet de l'identifier (en fait, cet Id est attribué au local APIC de chaque processeur). Certains bits de ce registre permettent de préciser quel est le type de transfert : doit-on envoyer l'interruption au processeur émetteur, à tous les autres processeurs, à un processeur particulier. Dans le dernier cas, certains bits du registre permettent de préciser l'Id du processeur qui va devoir recevoir l'interruption. A charge de l'APIC de faire ce qu'il faut en fonction du contenu de ce registre.

www.siteduzero.com

Partie 3 : Le partage de la mémoire

39/97

Partie 3 : Le partage de la mémoire
Dans les chapitre précédents, je n'ai pas arrêté de dire que le partage de la mémoire posait de nombreux problèmes qu'il fallait résoudre d'une façon ou d'une autre. Mais j'ai volontairement passé sous le tapis ces problèmes. Dans cette partie, nous allons voir en détail quel genre de désastres peuvent être dus au partage de la mémoire.

Cohérence mémoire
Comme je l'ai dit dans ce tutoriel, partager la mémoire RAM entre plusieurs processeurs n'est pas une chose facile et pose de nombreux problèmes. Et c'est maintenant que l'on va rentrer dans les vif du sujet et parler de ces fameux problèmes en détail. Lorsque ces threads doivent manipuler une même donnée, les ennuis commencent. Il faut savoir que dans de telles situations, la gestion des caches pose de sacrés problèmes, capables de faire planter un programme en un rien de temps. Pour illustrer ces problèmes, je vais commencer par introduire la situation par un petit exemple assez simple.

La cohérence : c'est quoi ?
Imaginons que deux processeurs manipulent une donnée : ceux-ci ont une copie de la donnée dans leur cache qu'ils manipulent et modifient à loisir. Si un processeur modifie cette copie de la donnée et que celle-ci est enregistrée dans son cache ou en mémoire, elle sera alors différente de celle présente dans le cache de l'autre processeur.

Ce qui fait qu'un processeur peut continuer à manipuler une donnée périmée qui vient d'être mise à jour par l'autre processeur. C'est pas bon ! Il faut corriger ça. D'autres situations peuvent faire en sorte que le contenu du cache devienne périmé, et les écritures dans une mémoire cache ne sont pas les seules à poser problème. Derrière ce problème, se cache deux problématiques assez complexes : pour commencer, il faut faire en sorte que notre processeur ne puisse pas lire une donnée périmée et faire en sorte d'avoir les bonnes valeurs dans la mémoire cache et/ou sa mémoire : on parle de cohérence mémoire ; et ensuite, il faut faire en sorte que nos écritures ou lectures en mémoire (ou dans les caches) soient prises en compte dans le bon ordre, et s'occuper de faire les mises à jour au bon moment : on parle de consistance mémoire. Pour simplifier, la cohérence porte sur la valeur de la donnée, et se préoccupe du contenu de la donnée. Elle s'occupe du "quoi" mettre à jour, et avec quelle valeur. La consistance s'occupe du "quand" mettre à jour. Chacun de ces deux problèmes impose des contraintes techniques distinctes. Nous allons commencer par la cohérence mémoire, qui est plus simple à aborder. Un processeur doit toujours éviter de se retrouver avec une donnée périmée et doit toujours avoir la valeur correcte dans ses

www.siteduzero.com

Partie 3 : Le partage de la mémoire

40/97

caches : il doit maintenir la cohérence des caches . Pour cela, la seule solution est d'utiliser des mécanismes permettant de faire en sorte que ce genre de cas n'arrivent pas.

Mécanismes de base
Lire des données est rarement un problème en terme de cohérence mémoire : ce sont surtout les écritures qui vont être la cause des divers problèmes de cohérence mémoire. Ce sont les écritures en mémoire RAM, ainsi que les écritures dans la mémoire cache qui vont poser problème. On va commencer par regarder ce qui se passe lors d'une écriture dans la mémoire cache.

Politique d'écriture du cache
Si on écrit dans la mémoire cache, il se peut que le contenu de la mémoire RAM ne soit pas à jour. Cela peut poser quelques problèmes : un processeur qui veut lire une donnée depuis la mémoire n'aura pas la dernière version de la donnée, vu que la mémoire n'a pas encore été mise à jour.

C'est ce qui se passe avec les caches Write Back . Avec eux, le contenu de la mémoire n'est pas cohérent avec le contenu du cache. Avec les caches Write Back , le processeur écrit dans le cache contenant la donnée sans écrire dans la mémoire RAM et dans les niveaux de caches inférieurs (par exemple, le L2 et le L3 sont des niveaux de caches inférieurs au L1, plus "proche" du

www.siteduzero.com

com . l'implémentation des mécanismes permettant d'assurer la cohérence mémoire est plus facile : on est certain que la cohérence mémoire est assurée dans le cas où une donnée n'est présente que dans le ou les caches attribués à un seul processeur. On doit attendre que la donnée soit effacée du cache pour l'enregistrer en mémoire ou dans les niveaux de caches inférieurs (s'ils existent). Write Through Avec les caches Write Through . toute donnée écrite dans le cache est écrite en même temps dans la mémoire RAM et dans les niveaux de caches inférieurs s'ils existent. Avec ces caches. C'est pourquoi il existe un autre type de cache : le cache Write Trought . et ne maintiennent pas la cohérence de la mémoire.siteduzero.Partie 3 : Le partage de la mémoire 41/97 processeur) s'ils existent. mieux adapté à ce genre de situations. les caches Write Back rendent plus difficile l'implantation de mécanismes de gestion de la cohérence des caches. Cela évite de nombreuses écritures mémoires inutiles. Avant Après www.

siteduzero.com . Avant Après www. Mais ce n'est pas forcément le cas pour les données stockées dans les mémoires caches des autres processeurs. La cohérence des caches n'est pas maintenue.Partie 3 : Le partage de la mémoire 42/97 Le contenu de la mémoire est donc toujours le bon.

Le cache est conçu pour. Avec eux. si on écrit celle-ci. V ous remarquerez que sur le schéma. notre processeur aura donc toujours accès à la www.Partie 3 : Le partage de la mémoire 43/97 Ces caches ont aussi de nombreux défauts en terme de performances. si on utilise un cache Write Back ). on peut être sur que les autres processeurs liront la donnée mise à jour et non une ancienne version.siteduzero. ce qui peut saturer le bus reliant le processeur à la mémoire. et iront toujours chercher la donnée dans le cache. la mémoire RAM contient encore une autre version de la donnée (du moins. une donnée n'est pas dupliquée en plusieurs exemplaires. Ainsi.com . mais n'est présente qu'une seule fois dans tout le cache. Au final. Mais cela ne pose pas de problème : les processeurs ne pourront pas accéder à la donnée en RAM. Ils ont notamment tendance commettre beaucoup d'écritures dans la mémoire RAM. Les performances peuvent s'en ressentir. Caches partagés Les caches partagés ne posent aucun problème de cohérence.

Ce protocole est ce qu'on appelle un protocole de cohérence des caches . Quoiqu'il en soit. comme vous pouvez en juger par cette liste non-exhaustive : MSI protocol . Firefly and Dragon protocol . Protocole MSI Le plus simple de ces protocoles s'appelle le protocole MSI. si un contrôleur mémoire va écrire en mémoire RAM. etc. Pour les curieux. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM. les mémoires caches ne sont pas vraiment les seules fautives dans ce genre de situation. sans passer par la ou les mémoires caches. Mais ces techniques de base ne résolvent pas tout ! Les caches Write Trough permettent de maintenir la cohérence entre des niveaux de cache différents (L1-L2 ou cachemémoire. Pour ce faire. on ne peut pas partager tous les caches : partager le L1 n'est pas possible sans grosses contreparties que n'importe quel concepteur de processeur voudrait éviter à tout prix. etc. qui permet aux périphériques d'aller lire ou écrire en RAM. Il existe beaucoup de protocoles de cohérence des caches. aussi appelé Illinois protocol . dans ce chapitre : Communication avec les entrées-sorties. on dispose de certaines solutions de base. MERSI . on interdit de charger dans le cache des données stockées dans les zones de la mémoire qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. celui-ci est détaillé dans mon tutoriel sur le fonctionnement d'un ordinateur depuis zéro. Ce protocole se contente simplement de donner à chaque donnée www. les modifications faites en mémoire ne seront pas répercutées dans les mémoires caches. ce qui se fait parfois en se servant de la mémoire comme intermédiaire. on va simplement dire qu'il s'agit d'un intermédiaire. Le processeur n'est pas toujours le seul composant dans notre ordinateur capable d'aller écrire dans la mémoire : certains périphériques peuvent ainsi aller directement lire ou écrire dans la mémoire RAM. write-once . 44/97 Direct Memory Acces Malheureusement. De même. MESIF . Protocoles de cohérence des caches Pour limiter la casse. on a inventé d'autres mécanismes qui se chargent de déterminer quelles sont les données périmées et de les remplacer par des données valides. MOSI . comment échanger les données entre caches. D'autres phénomènes peuvent mettre le bazar. Pour simplifier. Mais ils ne servent à rien si une donnée est copiée dans les caches L1 de deux processeurs différents. ces périphériques utilisent un circuit intermédiaire : le contrôleur DMA. Ces solutions sont donc limitées. qui décrira quand considérer qu'une donnée est périmée.com . Conclusion Comme on l'a vu. MESI. Ces circuits vont fonctionner suivant un certain protocole. ou entre un cache L1 et un cache L2 associés à un processeur. Et utiliser des caches Write Through n'y changera rien ! Pour résoudre ce problème.Partie 3 : Le partage de la mémoire dernière valeur écrite par les autres processeurs. On peut les utiliser pour maintenir la cohérence entre le cache et la mémoire.siteduzero. ne pas copier certaines données dans le cache. Synapse . qui peuvent fortement limiter la casse. MOESI . utiliser des caches Write Trough . mais ne peuvent rien pour les autres situations. comme partager les caches. Ces mécanismes mettent à jour les données dans les différents caches en copiant les données mises à jour d'un cache vers un autre. Berkeley . etc. Ces mécanismes sont implémentés dans différents circuits présents dans nos processeurs ou sur nos cartes mères. par exemple).

Quand on veut lire une donnée devenue invalide. et seul cet autre processeur a une donnée valide (qui est dans l'état Modified ). si elle a été modifiée. et aussi en mémoire. Le protocole MSI définit trois états : Modified . qui permet de savoir si cette donnée est présente dans les autres caches.Partie 3 : Le partage de la mémoire 45/97 stockée dans le cache un état. la donnée dans l'état Modified (dans le cache du processeur qui a modifié la donnée) et Invalid (dans les autres caches) repassent dans l'état Shared . Cette donnée est donc à jour. Une fois les données mises à jour. C'est seulement quand on veut lire une donnée périmée que la mise à jour a lieu. Retenez bien ce détail pour la suite du tutoriel.com . L'état Shared correspond à une donnée à jour : cet exemplaire de la donnée est présent dans tous les caches du processeur. Invalid . le processeur va lire la bonne donnée en mémoire RAM ou dans un autre cache qui possède la bonne version de la donnée. Enfin. etc. Fonctionnement Quand un processeur veut aller lire dans le cache. pas avant. il se retrouve face à deux solutions : soit la donnée dans le cache est dans l'état Modified ou Shared . L'état Modified correspond à une donnée qu'on a modifiée dans le cache. soit elle est dans l'état Invalid . et il devra aller récupérer la bonne version en mémoire RAM ou dans un autre cache. Shared . www. l'état Invalid correspond au cas où la donnée présente dans le cache est périmée : un autre processeur a mit à jour l'exemplaire présent dans son cache.siteduzero. et le processeur peut l'utiliser sans problème . mais les autres copies présentes dans les autres caches du processeur ou en mémoire sont périmées. V ous remarquerez que les initiales de ces états ne sont rien d'autre que le nom du protocole.

on utilise soit le protocole MOESI. et sera dans l'état Shared sinon. tous les caches ayant la bonne version de la donnée (la version Shared ou Modified ) vont répondre en même temps à cette demande. Mais si on a plusieurs mémoires caches. et leurs concurrents d'AMD. Protocole MOESI Le protocole MESI est un protocole de cohérence des caches qui était assez utilisé dans pas mal de processeurs. ou si seul un seul cache utilise cette donnée.com . on doit l'écrire dans la mémoire. c'est que le fonctionnement du protocole MESI est identique au protocole MSI. avec quelques ajouts. On peut ainsi optimiser nos protocoles MESI et MSI pour transférer nos données entre les caches sans passer par la mémoire. c'est simplement que d'autres protocoles sont utilisés sur les processeurs modernes. alors il ne sert à rien de prévenir inutilement. Par exemple. soit en Shared (les autres caches en possèdent une copie). et gaspillé des communications entre caches ou entre cache et mémoire pour rien. Si notre processeur ne dispose que de deux caches. et pour cause : le premier processeur à l'avoir utilisé n'est autre que le processeur Pentium premier du nom ! Ce protocole a été utilisé sur de nombreux processeurs. www. 46/97 Protocole MESI Ce protocole MSI n'est pas parfait. Ce protocole MESI rajoute un état supplémentaire : Exclusive. elle passe en état Modified . si la donnée est présente dans plusieurs caches. je ne vais pas vous donner un schéma pour illustrer le fonctionnement du protocole MESI : un tel schéma serait illisible. on se retrouve face à plusieurs cas : si la donnée est dans l'état Modified . Sur les processeurs modernes. Avec le Shared du protocole MSI. on a inventé le protocole MESI. Une donnée marquée Exclusive peut devenir Shared si la donnée est chargée dans le cache d'un autre processeur. on peut éviter l'envoi de messages aux autres caches (ou aux circuits chargés de gérer la cohérence des caches) si on écrit dans une donnée marquée Exclusive : on sait que les autres caches ne possèdent pas de copie de la donnée. Quand le processeur écrit dans une donnée. Si je parle au passé. et donc d'aller y écrire souvent : à chaque fois qu'une donnée passe dans l'état Modified . des problèmes peuvent survenir. Si on écrit dans une donnée marquée Exclusive. si aucun autre cache ne possède cette donnée. et c'est soit la mémoire soit l'autre cache qui va répondre. Pour régler ce problème. un tel protocole ne permet pas de savoir si une donnée présente dans un cache est présente dans les autres caches. Et inversement.siteduzero. Exclusive est une sorte d'amélioration de Shared . Cela nécessite de maintenir la mémoire à jour. Par exemple. Vu que la mémoire RAM est au minimum 20 fois plus lente que la mémoire cache. on aura prévenu pour rien. les performances s'effondrent et votre processeur devient aussi rapide qu'une antiquité du style 486DX. Mais dans le protocole MESI. on ne sait pas si les autres caches possèdent eux aussi une copie de la donnée. Ces communications entre caches ne sont pas gratuites et en faire trop peut ralentir fortement notre ordinateur. c'est autre chose. Fonctionnement Cette fois-ci. jusqu’aux processeurs Core d'Intel. elle reste dans cet état . Évidemment. elle passe soit en Exclusive (les autres caches ne contenaient pas la donnée). soit aller la chercher dans un autre cache. si la donnée est dans l'état Shared . Il y a deux solutions : soit aller chercher la bonne version en mémoire. Par exemple. rien ne change. comme le protocole MESIF. Tout ce qu'il faut retenir. Ce protocole est assez connu. Autant dire que gérer une situation dans laquelle tous les caches se marchent sur les pieds est loin d'être facile ! Seule solution : ne pas aller chercher la donnée dans les autres caches et passer uniquement par la mémoire. Avec cette distinction.Partie 3 : Le partage de la mémoire Pour l'écriture. Les mises à jour de données Invalid se font donc via la mémoire RAM. l'état Shared du protocole MSI est scindé en deux états pour faire cette distinction : une donnée sera dans l'état Exclusive si les autres processeurs ne possèdent pas de copie de la donnée . on doit prévenir tous les autres caches (ou un circuit qui est chargé de gérer la cohérence mémoire) qu'on vient de modifier cette donnée. une donnée Shared peut devenir Exclusive si tous les autres caches se débarrassent de cette donnée. et elle passe donc dans l'état Modified . la situation est très simple : notre processeur va ainsi faire sa demande. si une donnée est chargée depuis la mémoire pour la première fois dans un cache. soit un de ses collègues. V ous devez surement vous poser une question : pourquoi ce protocole a-t-il remplacé le protocole MESI ? V oyons cela par un exemple ! Imaginons qu'un processeur veuille obtenir la bonne version d'une donnée. si on veut modifier cette donnée. Ainsi.

Avec MOESI. Ce gros circuit. comme toutes les autres mémoires. Avec un directory protocol . On diminue fortement le nombre d'écritures en mémoire. Ces protocoles sont surtout utilisés sur les architectures distribuées : ils sont en effet difficiles à implémenter. les circuits gérant le cache vont autoriser la lecture de la ligne de cache. cette communication est facilement implémentable sur les processeurs actuels. Quand on veut lire une donnée. Ces informations sur l'état des lignes de caches sont souvent stockées en mémoire RAM : notre directory est ainsi fortement relié à la mémoire RAM. La mise à jour de ces bits se fait lorsqu'un processeur doit aller écrire dans le cache. Plus précisément. divisées en cases mémoires. et passerons leur donnée en version Modified .com . le directory. A chaque écriture dans une donnée partagée. Généralement. Généralement. si ces bits disent que la ligne de cache est valide. nos caches sont interconnectés entre eux. pour savoir si elle est partagée ou non : ces informations étant stockées dans un circuit spécialisé qu'on appelle le directory. Dans un cache. évitant ainsi d'aller chercher la donnée à jour en mémoire. ces blocs ont une taille assez grosse comparé aux cases mémoires : cela peut varier de 64 à 256 octets. afin de maintenir la cohérence. il n'y a pas besoin d'aller écrire en mémoire RAM la bonne version de la donnée. qui stockent des informations sur chaque donnée. on va alors lire les données depuis la RAM. Directory Protocol Notre mémoire cache est. Ainsi. Cela a un autre avantage : on n'est pas obligé de maintenir la mémoire à jour. ce cache devra prévenir tous les autres caches quand on écrira dedans. ce bus est partagé entre tous les caches. Les performances s'envolent ! Implémentation Il existe deux grands types de protocoles utilisées pour maintenir la cohérence des caches : les snooping protocols. En soit. Si un processeur écrit dans son cache. Ce directory est mis à jour à chaque fois qu'un processeur écrit dans sa mémoire cache : dans ce cas. sans rien faire de spécial. seul un seul processeur pourra avoir une donnée dans l'état Owned à la fois. voire totalement implanté dedans. Si un cache contient une donnée partagée. si telle ligne de cache est présente dans plusieurs caches. le protocole MOESI va simplement rajouter un état supplémentaire pour nos données : l'état Owned . il mettra sa donnée en Owned . voire Shared une fois la mémoire mise à jour. et remplacer les données pourries par la valeur correcte. lorsqu'une donnée passe en état Modified . qu'on peut modifier individuellement. si la ligne de cache est périmée. et les directory protocols. Ainsi. les circuits chargés de lire dans le cache vont vérifier ces bits. si telle ligne de cache contient une donnée périmée. on utilise un circuit spécial qui contient des informations sur toutes les lignes de caches présentes dans notre processeur. tandis que leurs concurrents sont plus simples à implanter sur les machines à mémoire partagée. www.Partie 3 : Le partage de la mémoire 47/97 Solution du MOESI Pour éviter ce gros problème. etc. seul le processeur qui possède la donnée en Owned va répondre à cette demande (ceux possédant la donnée Shared ne répondant pas à la demande formulée par le processeur demandeur). Sur nos ordinateurs actuels. ces cases mémoires sont regroupées en blocs de taille fixe qu'on appelle des lignes de cache. Snooping protocols Ces concurrents sont ce qu'on appelle les Snooping protocols . sait donc quelle est la ligne de cache qui a la dernière version valide d'une donnée présente dans plusieurs caches. dans lesquels chaque donnée contient des informations pour savoir si elle est partagée entre plusieurs processeurs .siteduzero. il devra donc prévenir ses collègues qui se chargeront alors de mettre à jour la donnée partagée s'ils en possèdent une copie. une série de fils qui permet de transférer des données entre le cache et la mémoire. nos Snooping protocols n'ont rien de bien compliqué : chaque ligne de cache contient des bits qui permettent de savoir si cette ligne de cache contient une donnée périmée ou si la ligne de cache est valide. Par contre. La raison est très simple : tous les caches sont reliés à la mémoire via un bus. le processeur va mettre à jour le directory automatiquement. Les autres caches se mettront alors à jour.

Ah. certains utilisent le Write Trough . et vont mettre à jour les informations sur les lignes de cache. Write Invalidation et Write Broadcasting Pour compliquer un peu les choses. on met à jour le plus tôt possible. on ne peut pas accéder au cache. Et ne parlons pas des processeurs utilisant des caches L1. je crois que quelques explications s'imposent. maintenir la cohérence entre tous ces caches est un sacré challenge. On réutilise l'existant pour mettre en œuvre notre protocole de cohérence des caches. toute écriture dans un cache est propagée. toutes les autres versions de cette donnée. c'est que les données dans les autres caches sont modifiées automatiquement. Lorsque l'on cherche à lire une donnée périmée. le cache L1 n'est pas relié à la mémoire : c'est le cache L2. Mais le plus important est que ces caches ne sont pas tous reliés au bus mémoire. La différence. etc. non ? Bon. Comme vous pouvez le voir. Une solution est de faire en sorte que les caches L1 soient de type " Write Trough " (ou presque) : toute écriture dans le L1 doit être répercutée dans le L2. écrire dans des données partagées et utilisées par plusieurs Threads peut poser de lourds www. cette mise à jour se fait différemment suivant les processeurs : soit on utilise des techniques de Write Invalidation .. alors pour résumer. la mise à jour ne commence que lorsque l'on veut lire une donnée primée. avant même toute tentative de lecture. soit on utilise le Write Update. Par exemple. mais passons ceux-ci sous silence. sur un processeur contenant un cache L1 et un cache L2. considérées comme périmées. voire L4. On peut donc utiliser un Snooping Protocol pour le L2. sans devoir créer des circuits supplémentaires. mais pas pour le L1. certains étant partagés. et les autres caches sont mis à jour automatiquement. Bref. on trouve toute une hiérarchie de mémoires caches. et la bonne version de la donnée doit être transférée depuis la mémoire ou depuis d'autres caches. A chaque fois qu'une information est transférée sur ce bus. et d'autres non . présentes dans les autres caches sont invalidées. la première méthode. Avec le Write Update.Partie 3 : Le partage de la mémoire 48/97 On peut donc faire passer les échanges d’informations entre caches via ce bus sans aucun problème. qui l'est. Avec la Write Invalidation .siteduzero. même si le processeur ne cherche pas à les lire. le plus proche de la mémoire. Avec le Write Update. les différents caches vont regarder ce qui est transmis sur ce bus. Cohérence entre différents niveaux de caches Sur les processeurs actuels. L2 et L3. Il existe bien sûr d'autres moyens. la Write Invalidation est celle qui est utilisée dans les protocoles qu'on a vu précédemment : quand on écrit une donnée dans un cache. à jour.com . V ous remarquerez que c'est un avantage certain qu'on les snooping protocols sur les directory protocols. et d'autres les Write Back . voire les données de celle-ci..

Partie 3 : Le partage de la mémoire 49/97 problèmes. N'oubliez pas ce genre de choses quand vous programmez.com . messieurs ! www.siteduzero. qui peut parfois être une cause majeure de ralentissements dans des applications parallélisées sur plusieurs cœurs /processeurs. La cohérence des caches est quelques chose de compliqué. qui peuvent ruiner les performances assez rapidement.

de son contrôleur. V oyons un peu lesquels. En clair : ça fait des Chocapic ! Dans ce cas. la lecture commençant avant que l'écriture précédente soit terminée. Et si on ne fait rien contre. la consistance mémoire signifie que chaque lecture d'une donnée en mémoire doit renvoyer la dernière valeur de la donnée qui a étè écrite. et disparaissaient dès qu'on pouvait maintenir la cohérence de la mémoire. de la mémoire. et on a déjà eu un petit aperçu de ce que cela pouvait donner dans le chapitre précédent. et pas simultanément. En effet. celle qu'on trouvait en mémoire avant l'écriture. Ces lectures foireuses étaient dues à la présence de mémoires caches. Il existe en effet des solutions matérielles pour faire en sorte que nos accès à une donnée partagée se fassent les uns après les autres. Problèmes Pour éviter tout problème quand on exécute un programme. et on trouvé quelques solutions intéressantes. En principe. Par exemple. suivie d'une lecture. il faut aussi maintenir la consistance mémoire. Il faudra aussi résoudre d'autres problèmes plus ou moins variés. Il faut dire qu'une lecture ou qu'une écriture en mémoire RAM peut prendre un temps assez long et parfois variable suivant l'opération. Cela peut créer des tas de situations bizarres. Dans ce genre de cas. Mais la fête n'est pas finie : ces lectures foireuses. le résultat des lectures et écritures peut fortement varier suivant l'état et le fonctionnement du bus mémoire. même avec des mécanismes de cohérence des caches parfaits ! En effet. des spécialistes et des chercheurs ont cherchés des solutions. Ces fameuses solutions matérielles sont ce qu'on appelle des instructions atomiques . ou deux processeurs qui tentent d'effectuer des écritures simultanées. Intuitivement. Accès simultanés Premier détail assez important : les accès mémoire dans les données utilisées par plusieurs processeurs doivent être effectués uns par uns. une minute : on n'a pas déjà réglé de problème dans le chapitre précédent. on doit maintenir la consistance de la mémoire. les opérations de lecture en mémoire effectuées par notre processeur pouvaient renvoyer une valeur périmée. On y avait vu que dans certains cas.siteduzero. notre processeur ne doit pas lire de donnée périmée. et tout se passe comme si l'écriture avait eu lieu après la lecture. rien n’empêche à la lecture de finir avant l'écriture : notre lecture peut alors renvoyer l'ancienne valeur. comme je me plait à vous le répéter depuis le début de ce tutoriel. datant d'avant une écriture antérieure. Hé. Pour cela.. dans l'ordre. Et on peut renouveler ce genre d'expérience avec des écritures démarrées en série : l'ordre des écritures peut s'inverser : l'écriture la plus récente finissant avant l'autre. En clair. en parlant des protocoles de cohérence des caches ? Et bien non ! On a réglé les problèmes dus aux mémoires caches. Il faut absolument trouver un moyen pour que les lectures et écritures dans les données partagées soient effectuées unes par unes et ne puissent pas être interrompues. il est parfaitement possible qu'une nouvelle opération de lecture ou d'écriture démarre avant que la lecture ou écriture précédente soit terminée. On démarre donc une lecture après une écriture. La cohérence des caches n'est qu'une facette du problème et n'est donc pas une solution miracle.Partie 3 : Le partage de la mémoire 50/97 Consistance mémoire Exécuter plusieurs threads sur plusieurs processeurs peut avoir des conséquences assez fâcheuses. V ous pouvez ainsi avoir un processeur qui lit une donnée en même temps qu'un autre tente de l'écrire. imaginez qu'on lance une écriture. et de l'age du capitaine. la consistance mémoire n'est pas respectée : une lecture ne renverra pas la dernière valeur écrite en mémoire. Dans ce cas. qui ne donnent pas le bon résultat sont encore possibles. mais une ancienne valeur périmée.com . maintenir la cohérence de la mémoire n'est pas une solution miracle : les caches ne sont pas les seuls fautifs.. rien ne l’empêche. www. mais d'autres phénomènes peuvent faire en sorte que des lectures en mémoires renvoient des données périmées.

Il est ainsi parfaitement possible pour notre processeur de changer l'ordre d’exécution des instructions d'accès mémoire : il peut placer une lecture avant une écriture. Mais notre processeur doit se débrouiller pour que cela ne change pas le comportement du programme. L'impossibilité d'effectuer des réorganisations et optimisations légales sur l'ordre des accès mémoire est proprement rédhibitoire en terme de performances. qui fonctionnent sur un seul processeur. Si une écriture précède une lecture. Tout se passe comme si les instructions de chaque programme s'effectuaient dans l'ordre imposé par celui-ci. En clair : votre programme fonctionne parfaitement bien. V otre processeur et votre compilateur ne peuvent plus changer l'ordre des instructions d'accès mémoire. on doit faire quelque chose. Trop même : il interdit de nombreux cas de réorganisation qui seraient pourtant parfaitement légaux. cela se complique. etc). rien n’empêche nos processeurs de changer euxmême l'ordre d’exécution des accès mémoire. Mais elles ne garantissent pas que l'ordre correct des opérations d'accès mémoire sera respecté. On doit donc résoudre un problème : comment faire en sorte que nos accès mémoires effectués ou non dans le "désordre" ne posent pas de problèmes ? Toute la problématique est là : une lecture doit retourner la dernière valeur écrite dans l'ordre du programme. aucun processeur actuel n'utilise ce modèle de consistance. une écriture après une autre. Memory Ordering Sur les architectures avec un seul processeur (on passe sous le tapis les techniques d'Hyper-Threading.Partie 3 : Le partage de la mémoire 51/97 Ces instructions sont des instructions qu'on ne peut pas interrompre. mais il est plus lent qu'il ne le devrait. Bref. Comme je l'ai dit plus haut. et les autres processeur qui liront ces données écrites en mémoire auront droit à des données mises à jour dans le désordre. même si les mécanismes de cohérence des caches ont fait leur travail ! La cause : les divers mécanismes de Memory Disambiguation . Pour être franc. on est certain que la lecture renverra la bonne valeur vu qu'elle est censée s’exécuter après l'écriture. Il y a plusieurs solutions à ce problème. Un processeur doit impérativement exécuter ses instructions dans l'ordre imposé par le programme. Chaque instruction atomique qui doit aller lire ou écrire en mémoire va ainsi réquisitionner la mémoire pour elle toute seule et empêchera toute lecture ou modification de la mémoire tant qu'elle ne se sera pas terminée. et que mettre les instructions dans le bon ordre est une nécessité. Il est très simple à comprendre : aucune réorganisation de l'ordre des instructions n'est possible. Il dispose pour cela de techniques de Memory Disambiguation assez compliquées.. d'autres modèles de consistances ont été inventés. Cela empêche de nombreuses optimisations matérielles et limite grandement les possibilités d'optimisation des compilateurs. Ceux-ci permettent certaines www.com . Du moins. Séquential Consistency Le plus simple de ces modèles de consistance s'appelle la sequential consistency . Mais avec plusieurs processeurs. Bref. Les opérations d'écriture venant d'un processeur peuvent être mises dans un désordre complet. En effet. et que les lectures ou écritures des différents processeurs étaient exécutées les unes après les autres. Est-ce un problème ? Oui : ce modèle de consistance est vraiment strict. on s'attend forcément à ce que les lectures renvoient obligatoirement la dernière donnée écrite en mémoire. on est face à un léger problème. il en faut de la performance pour faire tourner des programmes de plus en plus programmés avec les pieds respectueux de la Loi de Wirth. c'est la théorie : les processeur modernes n'hésitent pas à modifier l'ordre d’exécution des instructions histoire de gagner en efficacité. Par exemple. toutes les réorganisations d'accès mémoire ne sont pas problématiques. Modèles de consistance On a vu plus haut que le réordonnancent des instructions effectué par notre processeur peut poser de graves problèmes. sans se préoccuper de ce que peuvent faire les autres processeurs. Ces solutions sont ce qu'on appelle des modèles de consistance mémoire. Et chaque processeur fait cela chacun dans son coin. Après tout. Et de nos jours. rien. notre processeur exécutera les instructions d'un programme une par une. une lecture avant une lecture. L'instruction sera ainsi la seule à accéder à la mémoire et toute autre instruction voulant y accéder pendant ce temps devra alors mise en attente. Ces lectures effectuées par les autres processeur vont ainsi renvoyer de vielles données à cause d'un mauvais ordonnancement des instructions d'accès mémoires. De telles instructions suffisent à garantir que nos écritures et lectures s’exécutent l'une après l'autre. pour recouvrir le temps d'accès au cache ou à la mémoire par des instructions de calcul. Coarse Grained Multithreading .. En clair : notre lecture ne renvoie pas le même résultat. Pas de possibilité de réorganisation. il devient alors beaucoup plus difficile d’exécuter des lectures en avance. Les conséquences sont directes : pas d'utilisation de techniques de Memory Disambiguation . etc. même avec plusieurs processeurs. mais pas avec plusieurs. Relaxed Consistency Pour permettre à ces optimisations légales.siteduzero.

une donnée ne peut pas être réutilisée tant que la donnée n'a pas finie d'être écrite en mémoire et/ou mise à jour dans le cache des autres processeurs. Tout se passe donc comme si l'écriture en question n'était pas in-interruptible. Ce modèle est assez simple : hormis une exception. cette réorganisation ne pose aucun problème. le premier modèle de consistance utilisé sur les processeurs x86 est apparu sur les premiers processeurs x86 et est resté en place sur tous les processeurs de marque Pentium. Mine de rien. Et oui. avec pas mal de restrictions. aucune transaction avec un périphérique ne doit être en cours. Le modèle de consistance des processeurs x86 a varié au cours de l'existence de l'architecture : un vulgaire 486DX n'a pas le même modèle de consistance qu'un Core 2 duo. ce modèle est vraiment strict ! www. si deux lectures se suivent et qu'il n'y a pas d'écritures entre les deux. rapide.siteduzero. et qui autorisent certaines instructions à ne pas être atomiques. Néanmoins. passons maintenant à la pratique. Mais cette ordre peut être un peu relâché. Ainsi. la lecture doit se faire dans la mémoire . cela nécessite quelques conditions : ces écritures doivent se faire dans la mémoire cache . Total Store Ordering Dans le cas le plus strict. ce qui a été fait plus haut pour l'écriture est alors possible pour les lectures : on peut parfaitement démarrer une autre lecture pendant que d'autres lectures sont en attente ou en cours. nos écritures doivent aller écrire à des adresses différentes de l'adresse accédée en lecture . ainsi que des réorganisations de l'ordre des écritures quand ces écritures manipulent des données différentes. on peut parfaitement lire la donnée écrite avant même que l'écriture soit terminée et que toutes les versions de la donnée soient mises à jour. Bref. le x86 est assez strict. La seule condition pour éviter les catastrophe est que ces écritures manipulent des données différentes. par exemple. rien n’empêche d'effectuer une lecture avant une écriture si jamais ces deux instructions manipulent des endroits très différents de la mémoire : les deux données étant indépendantes. dont ceux implantés dans nos processeurs actuels. ceux qu'on retrouve dans nos PC actuels. Partial Store Ordering L'autre possibilité est de permettre des écritures simultanées. placées à des endroits différents de la mémoire. et faire comme si les écritures étaient ininterruptibles. No Limit Enfin. En clair. Divers modèles de consistance existent donc. Si on compare aux autres processeurs. Et bien sachez qu'on peut faire encore plus pire : rien n’empêche de pouvoir modifier l'ordre des lectures effectuées sur une donnée. certains processeurs vont encore plus loin : ils autorisent toutes les réorganisations possibles entre lectures et écritures. Par exemple. Cette exception concerne les lectures : dans certains cas. on peut démarrer une écriture pendant qu'une autre est en attente ou manipule la mémoire. Au lieu d'attendre que toutes les écritures précédentes se terminent. c'est ce qui était fait dans les protocoles de cohérence des caches qu'on a vus précédemment : le statut modified n’empêchait pas les lectures de la donnée. on peut les exécuter avant certaines écritures. Mais on peut grosso-modo classer ces modèles de consistance en trois grandes catégories. Nous allons donc voir l'exemple des processeurs x86. qui permettent d'autoriser ou d'interdire certaines réorganisation. Difficile de faire plus laxiste comme modèle de consistance. Pareil pour deux lectures effectuées sur des données indépendantes : l'ordre des lectures ne changera rien. tout doit se passer comme si le processeur accédait à la mémoire dans l'ordre des opérations de notre programme. En fait. Quoiqu'il en soit. Dans cette partie. c'est limite s'il n'existe pas un modèle de consistance pour chaque jeu d'instruction existant au monde.com . on va voir les modèles de consistances utilisés sur quelques processeurs plus ou moins grand public. Ainsi. Exemples Après ces histoires de théorie pure et simple. les modèles de consistance des processeurs x86 ont toujours étés assez forts.Partie 3 : Le partage de la mémoire 52/97 réorganisations non-dangereuses. Un processeur peut ainsi réutiliser une donnée qu'il vient d'écrire dans son cache avant que cette donnée soit enregistrée en mémoire ou dans les autres caches. on peut démarrer une nouvelle écriture avant que les écritures précédentes ne soient terminées. C'est exactement ce qu'on attend de la sequential consistency. Autant dire que tous les présenter serait un peu long. et efficace. Simple. Je ne vous cache pas que c'est exactement ce qui se passe avec la majorité des modèles de consistances plus laxistes. De même. on peut effectuer ces deux lectures dans l'ordre qu'on veut. tant qu'elle se font sur des données différentes.

Ce sont donc les instructions MOVNTI. Après tout. www. j'ai mentionné le fait que les écritures en mémoire peuvent changer dans certains cas exceptionnels. si ces écritures et la lecture ne se font pas au même endroit. les choses changent. MOVNTPD. Ces instructions spéciales sont ce qu'on appelle des Memory Barriers.siteduzero. Mais ce ne sont pas les seules : le x86 possède quelques instructions permettant de travailler directement sur des chaines de caractères ou des tableaux : ce sont les instruction REPMOVSD. notamment) . il est impossible d’exécuter en avance les instructions placée après une Full Fence. le modèle de consistance a du être assoupli pour éviter de perdre bêtement en performance. Cela supprime donc toute possibilité de réorganisation entre les instructions placées avant la Full Fence et celles placées après. l'ordre des écritures dans la mémoire ne change pas : une écriture dans la mémoire ne peut pas être déplacée avant ou après une autre écriture . le modèle de consistance relaxé vu précédemment n'est rien d'autre que le modèle qui posait problème au départ : on a toujours des Store Queue. Le Pentium 4 est en effet le premier processeur à implémenter des techniques permettant d’exécuter plusieurs processus en parallèle. des caches non-bloquants. Avec ça. ainsi que quelques instructions supplémentaires (celles qui accèdent aux périphériques ou aux entrées-sorties. Ces instructions SSE sont les instructions qui permettent d'écrire des données sans passer par le cache. à la même adresse. Et bien sachez que les écritures effectuées dans ces instructions peuvent se faire dans un désordre complet (ou presque). Ce processeur est en effet le premier à utiliser l'Hyperthreading. Ces cas exceptionnels sont les écritures effectuées par les instructions de gestion de tableaux et de chaines de caractères. Fences et Memory Barrier Plus haut. Full Fence La première catégorie de Fence s'appelle les Full Fences. aussi appelées des Fences.com . MOVNTQ . Ces Full Fences sont des instructions qui vont ordonner au processeur de terminer toutes les écritures et/ou lectures qui la précède avant de passer à l'instruction suivante. J'ai dit plus haut que les réorganisations des lectures et écritures sont autorisées. des lectures peuvent être déplacées avant des écritures. En conséquence. on ne peut pas déplacer une écriture ou une lecture avant ou après une instruction atomique.Partie 3 : Le partage de la mémoire 53/97 Sur les processeurs à partir du Pentium 4. et des tas d'autres optimisations matérielles qui posent des problèmes de cohérence/consistance. ainsi que certaines instructions SSE. MOVNTPS. aussi quelques précisions sont les bienvenues. Comment nos processeurs font pour garantir que les accès à des données partagées vont se dérouler correctement ? Très simple : le processeur fourni quelques instructions de base pour éviter tout problème. et bien d'autres encore. mentionnées il y a quelques chapitres. j'ai parlé des modèles de consistance qui autorisent toute réorganisation sur des données non-partagées entre processeurs. REPSCACB. De même. les instructions placées après la Full Fence ne peuvent pas être exécutées après elles. une écriture ne peut pas être déplacée avant une lecture . Il en existe diverses catégories. Dans cette liste. à part pour quelques exceptions. même celles qui sont censées poser des problèmes de cohérence mémoire. V oici un résumé de ce modèle de consistance : une lecture ne peut pas être déplacée avant ou après une autre lecture . MOVNTDQ .

www. les processeurs x86 qu'on trouve dans nos PC possèdent de telles Full Fence : il s'agit de l'instruction MFENCE. on doit utiliser une Fence spécialisée dans les lectures. Pour donner quelques exemples. on doit charger l'adresse avant la donnée. ces Full Fence n'existent pas sur les processeurs Itanium. Il n'existe qu'un seul exemple de processeur connu qui puisse le faire : il s'agit du processeurs DEC Alpha.. et une autre pour les écriture : SFENCE. Par exemple. Il existe ainsi un Fences en charge des lectures. et peuvent ainsi avoir pleins de Fences. Par contre. Mais si le processeur est autorisé à déplacer une lecture avant une autre. Cela a ses avantages dans certaines situations. chacune dédiée à des cas particuliers. Pour faire en sorte que ces deux lectures (adresse et données) se fassent dans le bon ordre. tandis que la terminaison des lectures sera effectuée par une autre Fence. un processeur particulièrement innovant pour son époque. imaginons la situation suivante : on charge l'adresse mémoire d'une donnée depuis la mémoire dans un registre. dont la fameuse Full Fence MFENCE. Load/Store Fences Pour plus d'efficacité. certains processeurs procèdent autrement : ils possèdent deux Fences bien distincts. C'est parfaitement possible : notre processeur va d'abord charger l'adresse. et un autre en charge des écritures.Partie 3 : Le partage de la mémoire 54/97 Tous les processeurs n'implémentent pas de telles Full Fence . puis il utilisera une instruction qui ira lire à l'adresse indiquée dans le registre. le chargement de la donnée peut alors démarrer avant qu'on connaisse l'adresse. puis on cherche à copier la donnée de cette adresse dans un autre registre. Dans un cas pareil. On trouve donc une Fence pour les lectures : LFENCE. qui n’eut pas le succès qui lui était du.siteduzero. Certains processeurs utilisent un nombre de Fences plus élevé. Les processeurs x86 ne sont pas dans ce cas : ils utilisent trois Fences.com . Petite remarque : sachez qu'il est assez rare qu'un processeur change l'ordre des lectures. Il s'agit en effet d'un luxe que tous ne peuvent pas s'offrir. On peut ainsi décider de terminer toutes les écritures placées avant la Fence avec une instruction. Et pour le programmeur ? Mais qui se charge de placer ces Fences au bon endroit ? Et bien il s'agit d'un subtil mélange entre programmeur et compilateur. et deux Fences spécialisées..

En effet. En changeant de compilateur. www.pdf sur les Fences pourra vous être utile : Memory Barriers : a hardware view For Software Hacker's. Mais ce n'est qu'un aperçu qui cache énormément de choses : on n'a pas parlé des spécificités de chaque processeur. et cherchez un peu si ça vous intéresse. on peut dire au compilateur qu'une donnée doit toujours être lue depuis la mémoire RAM. voire pas de Fences du tout. Cela peut se faire en déclarant des variables en volatile. Mais les compilateurs ne le font pas forcément. qui parle en détail de ces mécanismes de consistance mémoire : Shared Memory Consistency Models. dans certains langages de programmation.Partie 3 : Le partage de la mémoire 55/97 Généralement. je ne peux que vous conseiller de regarder les documentations techniques de ces processeurs.com . Et voilà. si vous êtes programmeurs. le programmeur doive manipuler explicitement des Fences. avec un peu d'aide du programmeur. Bref. Et pourtant. un langage de programmation dans lequel on utilise directement les instructions du processeur. Cela arrive assez rarement. Allez sur les sites des différents constructeurs de processeurs. Dans ces cas là. Seul problème : le programme obtenu sera spécialisé pour un seul jeu d'instruction. Pour limiter la casse. Les curieux qui veulent se documenter sur le sujet d'une façon un peu plus générale auront vraiment intérêt à lire ce tutoriel suivant. vous ne savez pas quelles sont les différences entre le modèle des processeurs x86 et celui des processeurs PowerPc. vous avez donc un aperçu des grandes classes de modèles de consistance existants. Par exemple. et sera difficilement portable sur d'autres processeurs. mais ça existe. certains systèmes d'exploitations ou compilateurs peuvent aussi fournir des Fences explicites. histoire de vous renseigner sur le sujet. renseignez-vous un peu plus sur ces histoires. Dans certains cas. on peut se retrouver avec des Fences manquantes. encapsulées dans des bibliothèques ou cachées dans certaines fonctions. Bref. un compilateur peut placer ces Fences au bon endroit. un programmeur n'a généralement aucun moyen de dire à son compilateur qu'il faut placer une Fence à tel ou tel endroit. il y en a des différences entre ces deux modèles. Première solution qui vient à l'esprit : utiliser l'assembleur. la lecture de ce fichier . Toutefois. Mais ce n'est pas une obligation. C'est très utile pour préciser que cette donnée est potentiellement partagée par plusieurs processeurs ou manipulable par des périphériques. les compilateurs peuvent placer des Fences lors des lectures ou écritures sur ces variables. De plus.siteduzero.

et doit se généraliser à presque toutes les autres situations impliquant une donnée partagée. Chaque thread doit donc avoir un accès exclusif à notre donnée partagée. qui est augmentée de 1 deux fois. cette réponse peut. Seul problème. comme le modèle de consistance nous l'impose. blague à part. Mais cela ne résout pas tout. Il faut en effet lire la donnée. un seul thread doit pouvoir manipuler notre compteur à la fois. Sans cela. Pour cela. il n'est pas rare que plusieurs instructions appartenant à des threads différents veuillent accéder à une donnée simultanément. cela pose encore une fois des problèmes (vous devez en avoir marre de voir cette phrase. c'est que notre thread ne peut pas faire tout ce qu'il veut sur une donnée partagée en une seule instruction. Chose assez importante s'il en est. l'autre processeur ira lire une version de la donnée qui n'aura pas encore été modifiée par l'autre processeur. Notre donnée est un vulgaire nombre entier. Les instructions de nos threads s’exécuteront en série. cela suffit à maintenir la cohérence et la consistance de notre mémoire. Au niveau logiciel.. Dans notre exemple.Partie 3 : Le partage de la mémoire 56/97 Synchronisation entre Threads Comme je l'ai dit dans les chapitres précédents. mais le processeur peut parfaitement être dérangé par un autre processeur entre deux instructions. voici ce que cela donnerait. Et comme toujours. Et les Fences et autres instructions atomiques peuvent être d'une grande aide dans ce genre de cas. Il va devoir utiliser plusieurs instructions successives sur la donnée pour pouvoir en faire ce qu'il veut. augmenter de 1 la donnée n'est pas effectuée en une seule instruction sur les processeurs x86. chaque thread s’exécutant sur un processeur x86. puis l'écrire. sans qu'aucun autre thread ne puisse manipuler notre donnée. Nos processeurs sont pas foutus de faire des calculs tellement basiques qu'on pourrait les laisser à des enfants de 5 ans ! Bon.). on a appris à faire en sorte que des instructions individuelles puissent ne pas être interrompues. Le seul problème. Chaque thread peut être interrompu à n'importe quel moment par un autre processeur qui voudra modifier sa donnée. Chaque thread veut l'augmenter de 1 régulièrement. On a vu dans le chapitre précédent comment éviter que deux threads accèdent simultanément à une donnée : il suffit d’exécuter nos instructions les unes après les autres et interdire les accès simultanés.com . pour avoir le bon résultat il y a une seule et unique solution : le premier processeur doit avoir un accès exclusif à la donnée partagée. l'augmenter de 1. d'autres contraintes peuvent apparaitre.. on utilise des instructions atomiques. Exclusion mutuelle www.. Prenons un exemple : on utilise une donnée partagée entre plusieurs threads..siteduzero. on court à la catastrophe. On doit donc définir ce qu'on appelle une section critique : un morceau de temps durant lequel un thread aura un accès exclusif à une donnée partagée : notre thread est certain qu'aucun autre thread n'ira modifier la donnée qu'il manipule durant ce temps. Et si deux processeurs veulent augmenter simultanément cette donnée. Sections Critiques Dans les chapitres précédents. ce qui donne au final 6. Dans notre exemple. Et bien sûr. On a donc une valeur de départ de 5.

des Locks. sous peine de problèmes. Elle est notoirement utilisée sur les processeurs x86 de nos PC. décider si on peut le modifier. on va devoir ajouter quelque chose à chaque donnée à partager. aucun processeur ne peut aller manipuler la mémoire : notre instruction atomique va alors bloquer la mémoire et en réserver l'accès au bus mémoire rien que pour elle. in-interruptibles. Pour mettre en œuvre cette réservation/dé-réservation. Ces instructions permettent ainsi de créer des sémaphores. ou est stocké dans un registre du processeur. Cela peut se faire en envoyant un signal sur le bus mémoire. c'est le cas si la donnée est en mémoire et que le processeur est un peu stupide. Généralement. Il existe deux grandes solutions. et 1 si elle est libre. sur les processeurs x86. Elle permet aussi d'implémenter des compteurs concurrents. la vérification/modification du compteur vue plus haut peut se faire avec l'instruction test and set.. On peut reprendre l'exemple du dessus pour l'illustrer. Si c'est le cas.siteduzero. écrit en mémoire à coté de la donnée.Partie 3 : Le partage de la mémoire 57/97 Autant prévenir tout de suite : créer de telles sections critiques se base sur des mécanismes mêlant le matériel et le logiciel. Ces instructions peuvent ainsi lire ce compteur. capables d'effectuer cette modification du compteur en une seule fois. on fait en sorte qu'un seul thread puisse accéder à notre donnée partagée. Un thread qui veut manipuler cette donnée va donc attendre qu'elle soit libre pour la réserver afin de l'utiliser. Ce fameux résultat est fourni par l'instruction. et les mécanismes de cohérence des caches se contenteront de mettre à jour la donnée de www. il est possible que les deux threads lisent le compteur en même temps : ils liront alors un zéro.. qui implémentent cette instruction. On a alors crée ce qu'on appelle un verrou d'exclusion mutuelle. et va écrire un résultat en mémoire si ces deux valeurs sont différentes. on peut optimiser le tout dans le cas où la donnée est dans le cache. Dans le cas le plus simple. etc. puis une écriture si le compteur est à la bonne valeur. ce qui fait que nos instructions atomiques sont lentes. soit implantées directement dans le silicium de nos processeurs. cette vérification et modification du compteur se fait en plusieurs étapes : une lecture du compteur. il y a peut-être possibilité de faire en sorte que la vérification et modification de ce compteur puisse se faire correctement. Dans ce cas. V oici la plupart de ces instructions atomiques les plus connues : Instruction Description Cette instruction va lire une donnée en mémoire. ou pas d'autres mécanismes de synchronisation entre processeurs. Il faudrait que cette lecture et l'écriture se fassent en une seule fois.com . tous les autres threads devront attendre leur tour. fournies par des bibliothèques ou son langage de programmation. Cette instruction charge la valeur de notre compteur depuis la mémoire. Avec celle-ci. il devra tout simplement attendre son tour. Dans le cas le plus simple. retour à la case départ. ce quelque chose sera un simple compteur. Si la donnée est occupée par un thread . et essayeront alors de se réserver la donnée simultanément. ce compteur vaudra 0 si la donnée est réservée. et écrire la bonne valeur sans être dérangé par un autre processeur qui viendrait s'inviter dans la mémoire sans autorisation ! Par exemple. En effet. lors de l’exécution de l'instruction atomique. certains processeurs fournissent des instructions spécialisées. l'incrémente. Quoique non. Pour régler ce problème. ces instructions empêchent tout autre processeur d'aller lire ou modifier la donnée qu'elles sont en train de modifier. pas besoin de bloquer la mémoire : le processeur a juste à écrire dans la mémoire cache. Du moins. Si notre compteur est à 0. un programmeur n'a pas à devoir manipuler des instructions atomiques lui-même. et la libérera une fois qu'il en a fini avec elle. et que deux threads veulent lire et modifier ce compteur simultanément. V oyons la première de ces solutions : l'exclusion mutuelle. Quoiqu'il en soit. Si la donnée est réservée par un autre thread . Compare And Swap Fetch And Add XCHG Comme je l'ai dit plus haut. Leur rôle est toujours d'implémenter des verrous d'exclusion mutuelle plus ou moins sophistiqués. suivie d'une écriture en une seule fois. Ainsi. Instructions atomiques Seul problème : cette vérification et modification du compteur pose problème. le cout de ce blocage de la mémoire est assez lourd : cela peut prendre un sacré bout de temps. il réservera la donnée en passant ce nombre à 0. Celle-ci ne peut pas être interrompue. sur certains processeurs. D'autres instructions atomiques similaires existent pour résoudre ce genre de problèmes. Elle permet de réaliser ce qu'on appelle des sémaphores. qui peuvent être soit codées sous la forme de programmes. et écrit sa valeur en une seule fois. En tout cas. va comparer celle-ci à l'opérande de notre instruction (une donnée fournie par l'instruction). Bref. Cette instruction peut échanger le contenu d'un registre et d'un morceau de mémoire de façon atomique. mais ne fait que manipuler des abstractions basées sur ces instructions atomiques. Ainsi. qui indiquera si la donnée partagée est libre ou si un programme se l'est déjà réservée. en permettant d'effectuer une lecture. un thread qui voudra réserver la donnée va d'abord devoir vérifier si ce nombre est à 1 avant de pouvoir réserver sa donnée.

Cette instruction Store-Conditional écrit une donnée en mémoire à condition que notre donnée partagée en mémoire n'aie pas été modifiée depuis l’exécution de l'instruction Load-Link. Mémoire Transactionelle Matérielle Pour résoudre les différents problèmes posés par les verrous d'exclusion mutuelle. tout accès à la mémoire. sans avoir à bloquer quoique ce soit : les mécanismes de cohérence des caches s'occuperont alors de gérer la lecture et l'écriture de façon atomique. même inoffensif ou ne touchant pas à la donnée partagée devra attendre al fin de l'instruction atomique. Mais dans le cas contraire. De plus. Ces versions logicielles de la mémoire transactionnelle sont souvent beaucoup plus lentes que ce qu'on peut obtenir avec des verrous d'exclusion mutuelle. tous les autres threads seront définitivement bloqués. la transaction échoue et doit rependre depuis le début et tous les changements effectués par la transaction seront effacés. si on ne fait pas trop gaffe. une suite d'instruction qui va s’exécuter et faire ce qu'elle veut de la donnée partagée. Il existe plusieurs façons de l'implanter. elle est occupé par un de ses collègues indélicat qui sera passé avant. mais c'est malgré tout un défaut des verrous d'exclusion mutuelle. Maintenant.Partie 3 : Le partage de la mémoire façon atomique automatiquement. On n'a plus à utiliser ces instructions couteuses en terme de performance juste au cas où. on n'a pas besoin d'utiliser d'instructions atomiques au cas où un autre processeur voudrait aller modifier notre donnée partagée en même temps. on peut limiter la casse : par exemple. Cette mémoire transactionnelle est souvent implantée dans certains langages de programmation de façon purement logicielle. qui est toujours précédée d'une instruction Load-Link. Dans ce cas. Cette mémoire transactionnelle a quelques avantages sur les verrous d'exclusion mutuelle. cela commence à faire beaucoup. L'instruction Load-Link va lire une donnée depuis la mémoire de façon atomique. ces instructions atomiques bloquent totalement tout accès à la mémoire durant un certain moment. Dans ce cas. Elle est assez fréquente sur certains processeurs possédant peu d'instructions qu'on appelle des processeurs RISC. Deuxièmement. Une de ses technique s'appelle la mémoire transactionnelle. ce sont ce qu'on appelle des transactions . puis l'écrire. Si la donnée partagée est placée en mémoire. en utilisant des exclusions mutuelles bien cachées sous le tapis par le compilateur ou la bibliothèque utilisée. ces blocages peuvent être assez couteux. ainsi que sur certains processeurs MIPS ou ARM. tout s'est bien passé. Alors certes. tout se passe comme si notre transaction ne modifiait pas notre donnée. Premièrement. on n'utilise pas de verrous d'exclusion mutuelle. souvent placées avant ces instructions atomiques. Mais cette mémoire transactionnelle peut aussi être implantée de façon matérielle. Pendant que notre transaction s’exécute. Mais quoiqu'il en soit. les chercheurs en informatique ont inventés diverses techniques matérielles ou logicielles pour se passer de ces verrous. Déjà. et on va voir ce que cela donne pour les implémentations les plus connues. Si on rajoute le cout des Fences. Instructions LL/SC La première technique de mémoire transactionnelle matérielle est basée sur deux instructions : Load-Link et Store-Conditional. et plusieurs threads peuvent accéder à une donnée sans se la réserver. Le cout des instructions atomiques est alors fortement amorti. On peut alors écrire directement dans la mémoire cache. 58/97 Problèmes avec les verrous d'exclusion mutuelle Néanmoins. en forçant tous les autres à se mettre en pause. Enfin. on peut carrément rendre atomiques des morceaux de programmes complets. etc. Et c'est obligatoire. si la donnée partagée est dans la mémoire cache. celle-ci peut alors écrire son résultat en mémoire. elle est plus souple que ces verrous : il n'y pas de possibilités de blocages ou de bugs bizarres comme on en voit avec les verrous. il est possible qu'un thread n'aie jamais accès à la donnée partagée parce que tous les autres threads passent devant : à chaque fois que le thread regarde si la donnée est libre. Dans le cas où la donnée partagée n'a pas été modifiée par un autre processeur durant l’exécution d'une transaction. Deuxièmement : sur certains processeurs. La mémoire transactionnelle est basée sur un principe simple. Bref. Avec la mémoire transactionnelle. et le rendre visible à tous les autres processeurs. Ces morceaux de programmes atomiques.siteduzero. Cette transaction peut ainsi lire notre donnée. il est possible qu'un thread réserve la donnée en oubliant la libérer. pas besoin de bloquer la mémoire.com . Les autres thread doivent alors attendre que la donnée partagée soit libérée pour continuer leur exécution. Ces transactions ne sont rien d'autre qu'un morceau de programme. ils imposent qu'un seul thread puisse accéder à notre donnée. La création de nos sections critiques est alors assez différente de ce que l'on doit faire avec les verrous. le plus beau de tous. nos verrous d’exclusion mutuelle ne sont pas la panacée. Ce genre de chose est souvent synonyme de mauvaise programmation. Tout ce que fait notre transaction reste plus ou moins "invisible" des autres processeurs. on disposait d'instructions atomiques. ainsi que sur le fameux processeur Alpha. Elle est notamment utilisée sur les processeurs POWER PC. faire ses calculs. non pris en compte. cette technique des verrous d'exclusion mutuelle pose quelques problèmes. www. Avant. Vient ensuite l'instruction Store-Conditional. même s'ils veulent lire la donnée sans la modifier.

Bien sûr. Mais il arrive que dans certains cas. Si un seul processeur a manipulé la donnée partagée. ce qui limite la quantité d’instructions qu'on peut placer dans une transaction. Il peut y avoir un rapport ! Comme je l'ai déjà dit. Les autres processeurs peuvent éventuellement faire de même. Par contre. mais ils sont plutôt rares. l’exécution d'interruptions ou d'exceptions matérielles dans une transaction la fait échouer. Une fois la transaction terminé. Au lieu de devoir mettre un verrous et de réserver notre donnée juste au cas où. et plusieurs versions d'une même donnée différente sera présente dans les caches des processeurs après intervention des mécanismes de cohérence des caches. c'est qu'elle ne peut manipuler qu'une seule donnée partagée simple.Partie 3 : Le partage de la mémoire 59/97 Il arrive parfois que l'instruction Store-Conditionnal échoue alors que notre donnée partagée n'a pas été modifiée. Très lentes. Tout ce passe comme si les mécanismes de cohérence des caches étaient mis en pause lors du démarrage d'une transaction. Et parfois. Du moins. Mais si plusieurs processeurs ont modifiée cette donnée dans leurs caches. Avec ces techniques. Mais méfiez-vous : la mémoire transactionnelle peut aussi se trouver là où on ne l'attend pas. Notre instruction Load-Link va en effet charger la donnée partagée dans un registre : si on veut utiliser des données plus grosses que notre registre. Sur certains processeurs. www. on réserve une donnée partagée alors qu'aucun autre thread ne cherche à y accéder : la réservation est inutile. il n'est pas possible d'utiliser Load-Link. Si vous ne connaissez pas ce processeur. le processeur va ainsi modifier notre donnée partagée dans un de ses caches créant une nouvelle version de la donnée. Sans compter que ces instructions cherchent à installer des verrous qui empêchent à deux threads de s’exécuter simultanément. qui consiste à sauvegarder les registres du processeur. on peut agir d'une façon un peu plus optimiste. Il arrive parfois que des transactions échouent sans qu'un autre thread n'aie fait quoique ce soit. si la transaction foire. des chercheurs se sont penchés sur ce problème de réservations inutiles. Et ils ont trouvés une solution assez sympathique. c'est le cas sur certains processeurs : sur ceux-ci. Speculative Lock Elision Je suis sûr que vous ne voyez strictement aucun rapport entre les instructions atomiques et la mémoire transactionnelle.com . et le supercalculateur Mira du Argonne National Laboratory. notre processeur va incorporer quelques bits supplémentaires avec notre donnée. il suffit juste de vérifier combien de versions de la donnée se trouvent dans les caches : si on a plusieurs versions. les données modifiée par la transaction sont marquées Invalid par les mécanismes de cohérence des caches. Tout se passe comme si les mécanismes de cohérence des caches du processeur se remettent en route. chaque donnée dans le cache peut être présente en plusieurs exemplaires. ou faire un mélange entre mémoire transactionnelle et verrous. Nos processeurs permettent de le faire en réutilisant un circuit déjà utilisé dans d'autres circonstances : le Reorder Buffer. celle-ci ne sera présente qu'en une seule version dans les caches des autres processeurs. qui indiqueront son numéro de version. Généralement. basée sur la mémoire transactionnelle. les changements effectués par notre transaction sur les registres du processeur doivent être annulé si celle-ci échoue. on ne peut pas dépasser la taille d'un registre. pour accélérer l'exécution des instructions atomiques. Versions évoluées Mais ces instructions Load-Link et Store Conditionnal ne sont pas les seul mécanismes de mémoire transactionnelle matérielle. Mais c'est un défaut que certains processeurs n'ont pas. l’exécution de certaines instructions critiques peut ainsi faire échouer automatiquement une transaction par mesure de sureté. on peut citer le cas du processeur Blue gene d'IBM. et les modifications faites par celle-ci sont alors propagées dans les caches des autres processeurs si la transaction réussit. les changements sont propagés dans les autres caches. De même. les changements effectués dans le cache d'un processeur par une transaction ne vont pas être propagés par les mécanismes de cohérence des caches tant que celle-ci ne termine pas correctement. cela peut arriver pour d'autres raisons. on peut décider de sauvegarder une copie des registres du processeur au démarrage de la transaction. Pour cela. Ceux-ci se basent sur les mécanismes de cohérence des caches au chapitre précédent. Lorsque la transaction termine. Impossible de manipuler des données plus grandes qu'une certaine taille. Lors du démarrage d'une transaction. certains processeurs n'ont pas ce genre de problèmes. n'a pas ce problème. Pour connaitre la version de la donnée. La méthode concurrente. ils auront chacun crée une nouvelle version de la donnée. ou décider d'annuler les changements effectués lors de la transaction. Sur ce processeur. et on doit revenir aux verrous d'exclusion mutuelle. Mais celui a une taille limité à a peut-prêt une centaine d'instructions sur les processeurs modernes. Annuler les changements effectués lors d'une transaction n'est pas une chose simple. le matériel vérifie que les données manipulées par la transaction n’ont pas été accédées. D'autres mécanismes un peu plus évolués existent. Pour vérifier qu'une transaction a échoué. les instructions atomiques sont lentes. Aussi. c'est que les instructions atomiques sont utilisées de façon assez pessimistes : il arrive qu'on les utilise au cas où un thread irait aller modifier notre donnée partagée en même temps qu'un autre manipule notre donnée. sachez qu'il est utilisé sur deux des plus grands supercaculateur au monde : le supercalculateur sequoia du Lawrence Livermore National Laboratory. Son seul problème. la transaction a échouée et doit reprendre. mais on doit la faire au cas où. C'est des techniques de ce genre qui sont utilisées sur les processeurs récents.siteduzero. Comme exemple. Le problème.

XABORT quand à elle. et on la transforme en une simple instruction de lecture de la donnée partagée. si aucun autre thread n'a écrit dans notre donnée partagée. de nouveaux processeurs Intel sortiront sur le marché : ce seront les premiers processeurs grand public qui supporteront la mémoire transactionnelle matérielle. on n'a pas besoin de rajouter d'instructions. Le préfixe. et va remmettre le processeur dans l'état dans lequel il était avant le début de la transaction : les registres modifiés par la transaction sont remis dans leur état initial. Au alentours de mars 2013. va servir à stopper l’exécution d'une transaction : elle sert à faire planter notre transaction si jamais on s’aperçoit d'un problème lors de l’exécution de notre transaction. XEND et XABORT. le processeur va automatiquement reprendre à l'adresse indiquée par XBEGIN. il est intéressant de laisser le programmeur quoi faire. Les instructions atomiques peuvent ainsi supporter l’exécution en tant que transaction à une condition : qu'on leur rajoute un préfixe. on doit annuler les changements faits et les faire oublier. notre processeur va reprendre automatiquement son exécution à cette adresse. Pour plus efficacité. Toutes les instructions placées après elles dans l'ordre du programme seront ainsi dans une transaction. Bien évidemment.com . Au cas où la transaction échoue. Ceux qui veulent se renseigner sur cette technique plus en détail et qui veulent aller plus loin que ce misérable petit tutoriel de vulgarisation peuvent aller lire l'article original des créateurs de cette technique ici : Speculative Lock Elision. ou REPNZE. présentée dans ce papier. En cas d'échec de la transaction. vient le moment de libérer la donnée partagée via une autre instruction atomique. L'exemple avec le x86 Autre exemple. Une fois cela fait. Cela permet de donner des informations au code de gestion d'échec de transaction. correspond simplement à un octet optionnel. XBEGIN sert en quelque sorte de top départ : elle sert à démarrer une transaction. Ce mécanisme tente donc de se passer des instructions atomiques en les transformant en transaction une première fois. et sur l'implémentation de la mémoire transactionnelle matérielle de ces processeurs. deux modes sont disponibles pour la mémoire transactionnelle matérielle : le mode TSX. si jamais un autre thread se permet d'aller écrire dans notre donnée partagée. A la suite de l'échec de cette exécution optimiste. Celui-ci sert à stocker un code d'erreur qui indique les raisons de l'échec d'une transaction. qui permet de rendre certaines instructions atomiques. On trouve ainsi trois nouvelles instructions : XBEGIN. puis revient à la normale en cas d'échec. Il s'appelle le Lock Elision . Je pense notamment aux processeurs basés sur l’architecture Haswell. si on a de quoi marquer le début d'une transaction. cette transaction cachée. certains processeurs cherchent parfois à éviter ce genre de situation en estimant la probabilité que le premier essai (la transaction) échoue. Il arrive parfois que le premier essai échoue lamentablement : si un autre thread a beaucoup de chance de manipuler une donnée partagée en même temps qu'un autre. Par exemple. l'instruction XBEGIN permet au programmeur de spécifier une adresse. à une exception prêt : EAX. on n’exécute pas l'instruction atomique permettant d'effectuer une réservation. pour les instructions x86. tout ira pour le mieux : on pourra rendre permanents les changements effectués. ils incorporent un circuit permettant d'évaluer les chances que notre premier marche en tant que transaction : le Transaction Predictor. Pour cela. On remarquera que dans la version originale. on commence à exécuter la suite du programme en faisant en sorte que les autres processeurs ne voient pas les modifications effectuées sur nos données partagées. le processeur décide ou non d’exécuter ce premier essai en tant que transaction. www. Ainsi. et le mode HLE. Attardons-nous un peu sur ces processeurs. Cet octet servira à donner des indications au processeur. Pour cela. qui peut alors gérer la situation plus finement. Par contre. on peut citer les processeurs Intel récents. Évidemment. Sur ces processeurs.siteduzero. Cette adresse permet de pointer sur un morceau de code permettant de gérer l'échec de la transaction. il faut aussi indiquer sa fin. HLE Les processeurs Haswall supportent aussi le Lock Elision . le premier essai a de fortes chances de planter. qui permettront de modifier le comportement de notre instruction. placé au début de notre instruction dans la mémoire. comme on va bientôt le voir. Pour cela. TSX Le mode TSX correspond simplement à quelques instructions supplémentaires permettant de gérer la mémoire transactionnelle matérielle. qui permet de répéter certaines instruction tant qu'une condition est requise. les instructions atomiques servant à libérer la donnée partagée vont marquer la fin de notre transaction. au lieu de les interpréter comme des débuts e transactions. Lors de la fin d'une transaction. et va exécuter son programme normalement : le processeur effectuera alors de vraies instructions atomiques. Puis. A ce moment.Partie 3 : Le partage de la mémoire 60/97 Rien n’empêche de transformer nos instructions atomiques servant pour les réservations en instructions permettant de démarrer des transactions. les processeurs x86 supportent pas mal d'octets de préfixe : LOCK. le processeur reprendre son exécution au début de notre fausse transaction. Une fois cette situation connue. Ce n'est toutefois pas une obligation. on utilise l'instruction XEND.

Autrefois.Partie 3 : Le partage de la mémoire 61/97 Le fait est que certains préfixes n'ont pas de signification pour certaines instructions : les placer devant ces instructions n'a alors pas de sens. et XRELEASE qui dit que la transaction spéculative est terminée.siteduzero. tout en fonctionnant sur des processeurs plus anciens. et le processeur ne tenait pas compte de ces préfixes sans signification. Ainsi. un programme peut être conçu pour utiliser la Lock Elision . www. ils étaient totalement ignorés. ces préfixes sans significations sont réutilisés histoire de dire au processeur : cet instruction atomique doit subir la Lock Elision et doit être tentée en tant que transaction. Pour supporter le Lock Elision . Deux "nouveaux" préfixes font leur apparition : XAQUIRE qui sert à indiquer que notre instruction atomique doit être tentée en tant que transaction . qui ne la supportent pas ! Belle tentative de garder la rétrocompatibilité.com .

etc. et le débit binaire du bus devient limité. c'est un ensemble de fils qui relie la mémoire au reste de l'ordinateur : au processeur (via les mémoires caches. Prefetcher Contention Petit détail : nos processeurs effectuent pas mal d'accès mémoires cachés. On a supposé que seuls les processeurs étaient pertinents en terme de performance. et pas de bande passante : si vous lisez des sites d'informatique. Mais c'est un gigantesque abus de langage : n'importe quel électronicien vous dira qu'une bande passante en octets par seconde est une sombre fumisterie que seul un softeux peut se permettre. Il est assez rare d'atteindre de genre de limites dans la réalité.com . notre mémoire cache fait des siennes ! Bus mémoire Si jamais vous créez un programme utilisant plusieurs processeurs. etc). Si on abuse un peu sur les lectures/écritures en mémoire. expliquées comme il se doit dans la partie sur les mémoires du tutoriel Fonctionnement d'un ordinateur depuis zéro. aux entrées-sorties. cela peut venir de la programmation du logiciel : tous les logiciels ne se parallélisent pas facilement et certains problèmes ne se parallélisent pas facilement. certaines contraintes de performances se font plus gênantes qu'avant. Cela est du à de sombres histoires de bancs mémoires. Notre mémoire a un débit binaire assez énorme : on parle de plusieurs gibioctets par seconde dans le meilleur des cas. Il suffit que les programmes exécutés sur nos processeurs aient besoin de beaucoup accéder à la mémoire (à cause de caches plus efficaces ou de programmes ne tirant pas correctement parti de ces caches). Ne nous leurrons pas : la quantité maximale de donnée pouvant passer par de bus en une seconde est assez élevée. Mais passons. Seul problème : la quantité de données qui peuvent passer sur ce bus en une seule seconde est limitée. et les mathématiques sont censées être infaillibles. et il doit passer par ce bus mémoire. par exemple. indépendants. un circuit appelé le contrôleur mémoire et divers autres circuits. Le fait est que les hypothéses posées pour démontrer la loi d'Amdhal n'étaient pas les bonnes. ce bus mémoire sera alors à partager entre plusieurs processeurs. En effet. notre processeur est assez optimisé pour éviter d’accéder à la mémoire : il dispose de mémoires caches. Je m'explique : afin de mieux gérer nos mémoires caches. Ces processeurs doivent se partager divers composants matériels. diverses ressources. Ils doivent se partager le bus mémoire. Malheureusement. dans un bon langage. Généralement. Bref. et évitent donc de passer par le bus. tout dépend de la répartition des données en mémoire : les mémoires actuelles sont en effet assez fortes pour lire et écrire des données contiguës. et de timings mémoires. des Branch Predictors. vous aurez surement vu ce terme : bande passante. j'ai bien parlé de débit binaire. Sans compter que certaines optimisations de la conception d'un processeur permettant d'améliorer l’exécution de programmes parallèles peuvent avoir des conséquences néfastes sur les programmes séquentiels. qui peut devenir limité. Cela dépend aussi de la façon dont on y accède : alterner rapidement lectures et écritures sera moins rapide que n'effectuer des lectures. Avec un seul processeur et une application correctement programmée. En fait. C'est même ce qui se passe dans la plupart des cas. Sachez que lorsque l'on crée des programmes censés fonctionner sur des processeurs multicœurs. si vous faites cela. le cache. Il n'y a pas que les accès mémoires présents dans notre programme qui sont exécutés. intercalées entre les deux). notre bus mémoires a une rapidité limitée. Bien sur. Enfin bref. on pourrait se dire qu'il suffit de se baser sur la loi d'Amdhal pour prédire les gains de performances qu'on obtiendra en utilisant plusieurs processeurs. cela n’empêche pas les concepteurs de processeurs d'améliorer ceux-ci pour améliorer leurs performances sur des applications séquentielles. on ne peut pas avoir le moindre problème à cause de la vitesse de ce bus. Toutes les lectures et écritures de tous les processeurs doivent alors passer par ce bus. ce bus mémoire peut saturer. un débit maximal théorique : le bus commence en réalité à saturer un peu avant. Le bus mémoire. non-parallèles (amélioration des Prefetchers. Mais tout change avec plusieurs processeurs ! En effet. Mais le fait est qu'un système multiprocesseur n'est pas composé que de processeurs isolés. En étant un peu intelligent. mais la marge de manœuvre est assez faible. il est obligé de lire ou écrire directement dans la mémoire. vous obtiendrez un écart assez impressionnant avec la réalité. Cette quantité de données transmissibles par seconde sur le bus s'appelle le débit binaire du bus. Quand un processeur ne trouve pas sa donnée dans les caches.Partie 3 : Le partage de la mémoire 62/97 Partage des ressources L'utilité des architectures multicœurs et multiprocesseurs est évidente : la performance ! Si les entreprises qui fabriquent des processeurs se sont lancé dans la fabrication de tels processeurs. V oyons un peu pourquoi ! Bus Contention Premières ressources matérielles partagées : le bus mémoire et le contrôleur mémoire. et de nouvelles occasions de perdre en performances bêtement apparaissent. Mais on parle là de la limite la plus haute. On peut se demander les raisons : notre loi d'Amdhal a bien été démontrée il y a quelques chapitres. Et encore une fois. c'est simplement que c'est le seul moyen efficace qu'ils ont trouvé pour gagner de la performance sans trop faire d'efforts. On en avait déjà parlé dans le chapitre sur la Loi d'Amdhal. des politiques de remplacement des caches.siteduzero. vous n'aurez pas toujours un gain de performance égal au nombre de processeurs. Oui. qui évitent de nombreux accès à la mémoires. nos processeurs essayent de ruser www. Ce partage va limiter les performances.

le processeur qui manipulait la donnée supprimée va devoir aller relire sa donnée depuis la mémoire. on veut dire par là qu'il précharge beaucoup de données. cela ne pose pas de problèmes : tout dépend si la donnée effacée a des chances d'être réutilisée ou pas. et le laisse bien fonctionner : les pertes sont rares.5 mébioctet et un autre 0. ce choix est un des moins efficace : il faudrait mieux supprimer les données les plus récemment utilisées ! Il arrive aussi que l'on manipule des données vraiment grosses. on choisit souvent la donnée la plus ancienne du cache ou celle qui est la moins lue ou écrite. Si jamais on doit lire ou écrire une donnée de la mémoire dans un cache rempli. Ils peuvent ainsi spéculer sur la prochaine donnée à lire ou écrire. www. la solution à base de cache partagée est plus efficace. Si cette donnée n'est pas réutilisée après avoir été virée du cache par un autre thread . on peut obtenir une belle baisse de performances. Cette perte dépend de pas mal de facteurs. au lieu d'avoir deux caches de 1 mébioctet par processeur. chargées depuis la mémoire. Cette saturation arrive assez rapidement sur les processeurs qui utilisent un prefecther agressif. En gros. Dans certains cas. Généralement. qui ne rentrent pas trop dans le cache. Si cela arrive trop souvent. Si un thread a besoin d'utiliser 1. Par agressif. etc. et vérifier si le cache contient une copie de la donnée à aller chercher en RAM.5 mébioctet. une partie du débit binaire de notre mémoire a été utilisé à rien. on a un cache hit : on lit ou écrit dans le cache au lieu de la mémoire. Mais il arrive que le prefetcher se trompe. C'est ainsi : suivant le processeur utilisé et l'agressivité de son prefecther . il n'y a pas de problèmes. Pour être franc. Mais dans le cas contraire.Partie 3 : Le partage de la mémoire 63/97 un petit peu et sont capables de charger des données dans le cache de façon anticipée. Globalement.siteduzero. nos processeurs décident souvent de réutiliser les lignes de cache contenant les données les moins fréquemment utilisée ou les moins récemment utilisées. ligne de cache par ligne de cache. Dans ce cas. Ce faisant. Mais si ce choix n'est pas bon. Pour comprendre ce phénomène. Nos threads peuvent alors se marcher sur les pieds : c'est ce qu'on appelle un phénomène de Cache Contention . Or. et les performances s'effondrent. Ces prefetchers vont ainsi précharger des données. Nos processeurs peuvent en effet utiliser certaines régularités dans les accès mémoires d'un programme et essayer d'anticiper les besoins en données futurs. Lorsque notre processeur cherche à accéder à la mémoire. si vous manipulez des tableaux ou des structures de données assez linéaires (tableaux. Mémoire cache Autre ressource partagée qui peut poser quelques problèmes : les mémoires caches partagées. et cette capacité doit être partagée entre plusieurs threads. une des lignes de cache va être sélectionnée pour être écrite en mémoire RAM : elle deviendra libre et on pourra effacer son contenu pour y charger la nouvelle donnée. juste au cas où. Si tout ce passe bien et que le prefetcher ne se trompe pas. Pour cela. c'est un cache miss. les circuits qui s'occupent de gérer la mémoire cache vont intercepter la demande de lecture ou d'écriture. C'est une perte : on gâche un peu de débit binaire. En effet. Si c'est le cas. alors on gagner fortement en performance. son emplacement. les caches partagés ont beaucoup d'avantages : ils simplifient les mécanismes de cohérence des caches et les rendent plus performants. Chaque case mémoire d'une ligne de cache est numérotée afin de pouvoir y accéder individuellement. vous pouvez ou pas vous retrouver avec une saturation due au prefecther. si les préfetcher qui se trompent souvent. les problèmes commencent : on doit avoir assez de débit binaire pour alimenter nos programmes. Mais il arrive parfois que certains threads se marchent dessus à l'intérieur du cache. Un programme bien codé n'influe pas trop sur ce prefetcher . ce qui diminuera le débit binaire restant pour nos programmes : la saturation du bus n'est pas loin.com . Dans le cas contraire. En clair : faites attention à ne pas trop utiliser de mémoire (cache) pour éviter des baisses de performances assez subtiles dues à un manque de mémoire cache. Le seul problème. Un cache partagée n'a pas une capacité infinie. on peut modifier une case mémoire à l'intérieur d'une ligne sans trop de problème : le regroupement en lignes de cache ne compte que pour les transferts entre mémoire RAM et cache. ils vont gaspiller beaucoup de débit binaire à rien. cela ne pose pas trop de problèmes sur des applications normales. Petite précision : on est obligé de transférer une donnée du cache vers la mémoire (ou l'inverse). ils permettent d'augmenter la quantité de cache disponible pour un thread . il va falloir expliquer un peu plus en profondeur comment fonctionne une mémoire cache. en choisissant bien la ligne de cache à remplacer. ou que chaque thread aie de gros besoin en mémoire cache. plus les prefetcher de tous les processeurs. Cache Contention Notre mémoire cache est une mémoire un peu particulière : elle est découpée en blocs de taille fixe qu'on appelle des lignes de cache. Mais cela nécessite d'utiliser une partie du débit binaire disponible. Mais avec plusieurs processeurs. pas pour les transferts entre cache et registres. ces blocs ont une taille assez grosse comparé aux cases mémoires : cela peut varier de 64 à 256 octets. et notre donnée doit être lue depuis la mémoire RAM. Nos threads se marchent sur les pieds ! Dans pas mal de cas. alors vous pouvez avoir quelques surprises. Par exemple. il arrive parfois qu'un processeur aille effacer les lignes de cache occupées par un autre thread pour y placer des données à lui. Il y a un ou plusieurs circuits inclus dans notre processeur qui gèrent ces préchargement anticipés : ce sont les prefetchers. un cache partagé est plus souple. ou structures de données basées dessus) que vous parcourez en itérant à travers. on peut utiliser un cache partagé de 2 mébioctets. c'est qu'avec plusieurs processeurs. et surtout. Par exemple. ils peuvent décider de précharger certaines données avant que le processeur demande un accès mémoire.

Pour cela. dans la famille "comment diminuer par 1000 la rapidité de son application sans m'en rendre compte". les méthodes ou fonctionnalités de nos langages de programmation peuvent beaucoup jouer sur ce genre de choses : par exemple. et pour une raison simple : il est très difficle de le faire partir ! La seule solution consiste en effet à espacer nos données partagées en mémoire de façon à ce qu'elles soient placées dans des lignes de cache différentes. Un programmeur peut parfaitement tenir compte de la localité spatiale ou temporelle lorsqu'il programme. en utilisant les mécanismes de cohérence des caches. A ce titre. Il faudra trouver autre chose pour gagner en performances.com . Jusque là. Et parmi ceux-ci. il n'y a pas 36 solutions pour limiter les phénomènes de cache. Ceci dit. et doit.siteduzero. C++ et en ADA. Pour cela. le matériel évolue et dispose de quelques faibles marges de manœuvre. www. Partager la mémoire est acceptable tant que le nombre de processeurs n'augmente pas trop. On considère qu'au delà de 32 processeurs. une bonne utilisation du cache repose en réalité sur le programmeur qui peut. il verra que la ligne de cache est invalidée et devra alors récupérer la bonne version ailleurs. perdre de la performance bétement est quelque chose de vraiment facile. le sens de parcourt d'un tableau multidimensionnel peut faire une grosse différence. Et cela n'est pas possible dans tous les langages de programmation existants : c'est possible en C. Ce qui signifie souvent envoyer une requête sur le bus. pas de problème. je demande le False Cache Sharing ! False Cache Sharing . en programmation multicœurs. Il est parfaitement possible de se retrouver dans des cas où un programme parallélisé est plus lent qu'un programme n'utilisant qu'un seul cœur/processeur. et éviter le plus possible d’accéder à la mémoire. Mais passons.Partie 3 : Le partage de la mémoire 64/97 False Cache Sharing Mais les phénomènes de cache contention ne sont pas les seuls à poser problèmes : il en existe d'autres. Imaginez la scène : vous avez placé deux données à des adresses mémoires assez proches. Pour cela. le nom fait peur. Mais imaginez maintenant ce qui se passe lorsqu'un processeur accède à une de ces données : c'est toute la ligne de cache qui est invalidée par les mécanismes de cohérence des caches! Ainsi. Il est assez célèbre pour ses méfaits. voire fini par être contreproductif ! En clair : n’espérez pas avoir des processeurs possédant plus de 16 à 32 cœurs dans vos ordinateurs. et ce aussi bien au niveau de son algorithme : on peut citer l'existence des algorithmes cache oblivious . du choix de ses structures de données : un tableau est une structure de donnée aimée du cache. Il correspond au cas où deux threads se marchent mutuellement dessus lors de l'accès à des données placées sur une même ligne de cache. Bilan : des performances limitées par la mémoire au point que rajouter des processeurs ne sert à rien. tandis qu'une liste chainée ou un arbre n'en sont pas (bien qu'on puisse les implémenter de façon à limiter la casse). celle-ci se sont retrouvées dans la même ligne de cache. Le langage de programmation peut jouer. comme rapprocher l’architecture de notre processeur de celle d'une carte graphique. Lors du chargement de ces données dans le cache. mais placée sur la même ligne de cache. un code utilisant la programmation orienté objet ou un garbage collector sera souvent assez mauvais dans son utilisation du cache. et le saturer encore plus. prendre en compte le cache dès la conception de ses programmes. Mais ce problème est assez facile à comprendre. bus et prefetcher contention : il faut faire en sorte que les données à manipuler soient le plus possible dans la mémoire cache. mais d'autres langages comme le Java ne le permettent pas. une seule solution assez connue est de gérer manuellement ce qu'on appelle l'alignement mémoire de nos données. Avoir quelques pistes pour éviter ce genre de choses serait donc une bonne chose. voire aller chercher la bonne version de la ligne en mémoire. Solutions Bref. si un thread souhaite accéder à une autre donnée que celle modifiée par l'autre thread . le partage de la mémoire devient tellement couteux que les performances deviennent misérables : la mémoire n'est alors plus suffisamment rapide pour alimenter les processeurs en données et ceux-ci passent leur temps à attendre la mémoire. ou de son code source : par exemple. Ce phénomène peut faire baisser les performances de certaines applications assez rapidement.

Mais il ne travaillera pas sur les même données. et qu'on veut simplement rajouter des processeurs pour gagner en performances. les systèmes à mémoire partagée sont hors--jeu. et que ceux-ci sont loin d'être négligeables. chaque processeur possède sa propre mémoire. on a vu des systèmes multiprocesseurs dans lesquels les processeurs étaient placés sur la même puce de silicium (multicœurs) ou sur la même carte mère. qui exécutent des instructions différentes sur des données différentes (rappelez-vous le premier chapitre). et on peut gagner en performances en augmentant le nombre de processeurs assez facilement. qui leur sert à échanger des données ou des ordres un peu particuliers. On parle plus précisément d'architecture SPMD : Single Program Multiple Data . Certains ne se partagent pas la mémoire : chaque processeur a droit à sa propre mémoire. nous allons voir ces systèmes multiprocesseurs. Dans ce chapitre. Les couts de partage de la mémoire sont alors plus ou moins supprimés. Cette solution est acceptable tant que le nombre de processeurs n'augmente pas trop. Architectures Distribuées Dans les chapitres précédents. Cette demande va traverser le réseau local et arriver à son destinataire : la donnée demandée est alors envoyée via le réseau local et est copiée dans la mémoire locale de l’ordinateur demandeur. Il faudra trouver autre chose pour gagner en performances. et partageaient le même bus les reliant à cette dernière. Il va de soit que les communications entre les différents processeurs peuvent prendre un temps relativement long. voire fini par être contre-productif ! En clair : n’espérez pas avoir des processeurs possédant plus de 16 à 32 cœurs dans vos ordinateurs. et n'a pas à la partager. le partage de la mémoire devient tellement couteux que les performances deviennent misérables : la mémoire n'est alors plus suffisamment rapide pour alimenter les processeurs en données et ceux-ci passent leur temps à attendre la mémoire.siteduzero. on a vu des systèmes multiprocesseurs dans lesquels les processeurs étaient placés sur la même puce de silicium (multicœurs) ou sur la même carte mère. On considère qu'au delà de 32 processeurs. Ces processeurs se partageaient tous la même mémoire. chaque ordinateur exécute le même programme que tous les autres. comme rapprocher l’architecture de notre processeur de celle d'une carte graphique. Pas question de concevoir des supercalculateurs à 65536 processeurs en leur faisant partager une seule et unique mémoire. Tous les processeurs sont reliés entre eux via un réseau local. Bilan : des performances limitées par la mémoire au point que rajouter des processeurs ne sert à rien. Mais comme on l'a vu au premier chapitre. les systèmes à base de plusieurs processeurs ne sont pas tous organisés comme ça. Dans ce chapitre. et n'a pas à la partager. jusqu’à ce que la loi d'Amdhal vienne gâcher la fête.com . Du moins. Mais passons. les systèmes à base de plusieurs processeurs ne sont pas tous organisés comme ça. Ces architectures sont donc une sous classe des architectures MIMD. Généralement. C'est quoi ? Dans les architectures distribuées. Mais heureusement. Certains ne se partagent pas la mémoire : chaque processeur a droit à sa propre mémoire. Avec une organisation de ce genre. la qualité et les performances du réseau reliant les ordinateurs est très important pour les performances. nous allons voir ces systèmes multiprocesseurs. Si on a vraiment besoin de puissance pure. Les processeurs peuvent ainsi accéder à la mémoire d'un autre processeur via le réseau local : il leur suffit de faire une demande au processeur qui détient la donnée. Ces processeurs se partageaient tous la même mémoire. et partageaient le même bus les reliant à cette dernière.Partie 3 : Le partage de la mémoire 65/97 Partie 4 : Architectures NUMA et distribuées Dans les chapitres précédents. sans aucune mémoire partagée entre les processeurs. www.

Les disques durs sont une donnée critique sur les systèmes distribués. D'autres projets de calculs distribués accessibles au grand public ont vu le jour depuis SETI@home. Cela peut dépasser la centaine. On peut citer certains programmes de calculs scientifique comme Folding@home : vous pouvez ainsi faire travailler votre ordinateur pour la recherche médicale grâce à ce projet. Beaucoup de ces programmes de grid computing sont hébergés sur une plateforme centralisée nommée BOINC. De même. cette analyse étant effectuée par des ordinateurs reliés entre eux via internet.Partie 4 : Architectures NUMA et distribuées Bien sûr. Les exigences en matière de sureté sont très fortes : on ne doit pas perdre de données. et on doit éviter de perdre nos données si jamais un disque dur vient à tomber en panne. Mais il existe aussi une autre possibilité : rien n’empêche ces ordinateurs d'être regroupés ensemble via un réseau internet. on a inventé le RAID. L'ensemble doit continuer à fonctionner alors qu'un ou plusieurs processeurs ne fonctionnent plus. on peut récupérer les copies sur les autres disques durs. ces ordinateurs sont souvent reliés entre eux par des réseaux locaux : les ordinateurs sont physiquement proches les uns des autres. placer des mémoires caches ne pose strictement aucun problème : on n'a pas besoin de garantir la cohérence des caches avec ce genre de système. N'importe qui pouvait se connecter à ce projet et faire travailler son ordinateur pour se projet assez facilement. On parle alors de Grid Computing . De plus. toutes les histoires de synchronisation entre processeurs s'évanouissent et disparaissent pour de bon. Ce Grid Computing est assez connu : de nombreux projets assez connus dans le monde entier se basent dessus. ou dans le même bâtiment. Pour ce faire. Pareil pour les supercalculateurs. Différents types Reste que ces ordinateurs peuvent être regroupés de différentes façons. et si l'un tombe en panne. Des besoins en terme de sureté Les architectures distribuées ont souvent un grand nombre de processeurs. le système d'exploitation qui fonctionne sur les différents ordinateurs. toute défaillance matérielle ne doit pas altérer le fonctionnement de l'ensemble du système. toute perte de donnée doit être rendue impossible par divers mécanismes de sureté.com . ainsi que le programme exploitant tout ces ordinateurs doit être conçu pour résister à ce genre de problème. Dans la majorité des cas. Le premier de ces projets de Grid Computing à avoir étè connu du grand public n'est autre que SETI@home. Et pour cela. si vous prenez un serveur assez puissant. Dans ces conditions. Par exemple. accessible via ce lien. il serait dommage que notre beau serveur ou notre beau supercalculateur ne fonctionne plus alors qu'un seul processeur est en panne. Dans ce cas où les ordinateurs sont regroupés ensemble et sont géographiquement très très proches. Nos ordinateurs peuvent alors être très éloignés les uns des autres. Tous les ordinateurs seront regroupés ensemble dans un même bâtiment conçu spécialement pour l'occasion.siteduzero. ce Grid Computing a encore de beaux jours devant lui. le loi de Murphy n'est pas une option : il y a de fortes chances qu'une défaillance finisse par arriver tôt ou tard. La majorité des systèmes distribués utilisent le www. Quoiqu'il en soit. on parle souvent de clusters. et sont rassemblés dans la même pièce. par exemple. Le RAID est une technologie spéciale qui consiste à utiliser plusieurs disques durs sur lesquels on duplique nos données. celui utilise souvent plusieurs ordinateurs reliés entre eux via un réseau de ce genre. on peut lire des morceaux de données simultanément sur différents disques durs. Par exemple. acronyme de Redondant Array of Inexpensive Disks . On peut parfois gagner en performances : au lieu de changer un morceau de donnée à la fois à partir d'un seul disque dur. De même. rien n’empêche de mettre des mémoires caches entre la mémoire d'un processeur et celui-ci. un projet international visant à détecter des signaux extraterrestres. Cela permet de gagner en sureté : nos données sont dupliquées sur plusieurs disques durs. 66/97 Cette fois. Mais il s'agit du passé : le projet est aujourd'hui officiellement interrompu. Celui-ci analyse des signaux radio en provenance de l'espace.

ajouter quelques informations sur les destinataire. Pour information. que les processeurs s'envoient entre eux via le réseau. Dans ce cas. Le tout est très souvent plus lent que l'utilisation de verrous d'exclusion mutuelle. Les réseaux inter-processeurs Revenons maintenant à nos architectures distribuées. www. Cela permet d’empêcher à nos threads de se partager la mémoire explicitement : pas de verrous d'exclusion mutuelle. reçoit les données à traiter et se charge de répartir les traitements à effectuer sur les autres ordinateurs. ces messages ne passent pas par un réseau. L'ensemble est alors souvent secondé par un disque dur général sur lequel on va écrire les données finales. divers langages de programmation spécialisés pour les systèmes distribués implémentent des mécanismes dits de passage de message. via des réseaux locaux ou distants. C++. Cet ordinateur maitre peut aussi se chercher de gérer les défaillances ou les pannes. disponible sur un grand nombre de systèmes d'exploitation. et une connexion intelligente entre processeurs est une nécessité. La plus connue de toute est sans conteste MPI. relativement bien optimisés sur les architectures à mémoire partagées. Cet échange est ce qu'on appelle un envoi de message.com . Le destinataire reçoit alors le tout et peut commencer à travailler. Nos ordinateurs ne sont pas reliés n'importe comment. et leur vitesse. il est impossible de relier tous les processeurs entre eux directement : un processeur ne peut pas être relié à tous les autres directement. 67/97 Généralement. Du point de vue du logiciel. envoyer. etc. Cet échange prend la forme de messages. certains langages vont interdire tout partage de donnée entre threads et vont les faire communiquer par des envois de messages. Cette idée. nuisibles pour les performances et pour la santé mentale des programmeurs. Centralisée Pour les clusters. FORTRAN. qui fera ce qu'il faut avec cette copie. mais passent par la mémoire. et partager des données est donc impossible. au lieu de laisser nos threads partager des données. le cout en terme de performance se fait souvent sentir : l'envoi d'un message se base souvent sur des interruptions inter-processeurs et de nombreuses copies/recopies mémoires couteuse en performances. hiérarchique. un thread qui veut envoyer un message va copier sa donnée à un endroit de la mémoire avant d'envoyer la position de la donnée copiée à l'autre thread . le maitre. Cet ordinateur. Diverses bibliothèques assez connues permettent de fournir des primitives de passage de messages pour divers langages de programmation. Ocaml. Ceux-ci ne peuvent pas se partager des données : chacun a sa propre mémoire. chaque ordinateur d'un système distribué a son propre disque dur attitré. Un processeur peut ainsi préparer des données à envoyer à un autre processeur. commence à faire son apparition sur les systèmes multicœurs et multiprocesseurs à mémoire partagé. qui permettent de créer. On peut ainsi programmer quand et comment échanger nos données entre des processeurs. Python. Le succès de MPI est tel que certains processeurs implémentent MPI directement dans leurs circuits. etc. certains processeurs spécialisés possèdent des instructions qui permettent de prendre en charge diverses primitives de message passing directement dans le processeur. Ainsi. et pair à pair. Java. et recevoir des messages. nos ordinateurs sont tous esclaves d'un ordinateur principal. Bien sûr. Il existe trois grandes organisations de base : centralisée. Mais cela n’empêche pas à nos processeurs d'échanger des données entre eux. Ainsi. et pour un grand nombre de langages : C. les résultats des calculs.Partie 4 : Architectures NUMA et distribuées RAID. Ces réseaux. Il existe ainsi des processeurs spécialement optimisés pour la bibliothèque MPI. et même pire encore. et envoie le tout sur le réseau. On a vu que nos ordinateurs communiquaient entre eux via des réseaux locaux ou distants. ont une importance capitale pour la performance des architectures distribuées.siteduzero. autrefois utilisée uniquement sur les systèmes distribués. la mise en forme des données. Malheureusement. Le nombre de câbles serait beaucoup trop important. Communication inter-processeur Message Passing Nos processeurs sont donc reliés entre eux par des réseaux locaux.

Et demandez à un processeur de choisir une destination parmi 1576 : même résultat ! La solution consiste alors à regrouper plusieurs processeurs dans des espèces d’ensembles organisés de façon centralisée. avec lesquels il peut communiquer. Hiérarchique Quand le nombre de processeurs augmente. la version centralisée n'est pas forcément très adaptée. les solutions purement centralisée ou purement pair à pair ne marchent pas si bien que cela. www.com . tout processeur peut répondre aux requêtes envoyée par un autre : il n'y a pas d'ordinateur principal qui servirait d'intermédiaire qui gérerait toutes les requêtes des autres processeurs et se chargerait de les répartir sur les esclaves. Demandez à un ordinateur de gérer plus de 1576 processeurs à lui tout seul.Partie 4 : Architectures NUMA et distribuées 68/97 Pair à pair Pour le Grid Computing . mais les concepteurs de ce genre de système préfèrent relier nos ordinateurs de façon non-hiérarchique. De même.siteduzero. vous allez voir : l'ordinateur maitre va se mettre à fumer s'il n'est pas trop puissant. Tout processeur d'un tel système peut envoyer des requêtes à d'autres processeur. Dans ce cas. Elle peut l'être. chaque processeur est relié à un certain nombre de voisins.

Ensuite. il ne reste plus qu'à relier ces ensembles entre eux. comme illustré dans le schéma ci-dessous. www.Partie 4 : Architectures NUMA et distribuées 69/97 comme au-dessus.siteduzero.com . Cela peut se faire de façon centralisée. ou en Pair à pair.

C'est cette constatation qui a donné son nom aux architectures NUMA : NUMA est en effet l'acronyme de Non Uniform Memory Acces . le système d'exploitation peut jouer un rôle non négligeable : c'est en effet lui qui donne de la mémoire à nos programmes quand ils en ont besoin. Notre architecture NUMA devient donc un rassemblement d'architectures à mémoire partagées reliées via un réseau. On peut donc dépasser la fameuse limite des architectures à mémoire partagée. En l’occurrence. De quoi NUMA est-il ne nom ? Si un processeur veut accéder à la mémoire qui lui est attribuée. Ce sont les architectures NUMA. les architectures distribuées permettent de gagner en performance correctement au delà de la centaine de processeurs pour des programmes adaptés. En réalité. chaque processeur voit toutes les mémoires ce chaque processeur comme si elles étaient rassemblées dans une seule grosse mémoire unique. la vitesse de cette fausse mémoire ne sera pas la même. bien gérer les temps d'accès aux différentes mémoires est donc un plus bien agréable. Pour ce faire. Nos programmeurs peuvent ainsi programmer sur ces architectures comme s'ils utilisaient une mémoire partagée. mais le système d'exploitation et les différents processeurs se chargent de maintenir cette illusion. Mais ces architectures distribuées ont un gros défaut : on doit gérer les transferts et les échanges de données entre processeurs à la main. il est important de gérer la localité de nos données correctement (pauvres programmeurs. Pour faciliter la vie des programmeurs tout en gardant des performances décentes avec un grand nombre de processeurs. certains défauts des mémoires partagée n'existent pas sur ces architectures.. www.com . Quoiqu'il en soit. Accéder de préférence à la mémoire locale à notre processeur au lieu de devoir passer la un réseau pour aller lire dans la mémoire d'un autre processeur est en effet un gros gain en terme de performances. ces mémoires séparées sont "partagées". Avec cette méthode. En clair. et on peut en augmenter la puissance en rajoutant autant de processeurs que l'on souhaite. Mais le programmeur a aussi un rôle déterminant. Utiliser du message passing est assez lourd pour les programmeurs. et gérer les transferts de données entre processeurs est quelques chose de fastidieux. Elles ne possèdent pas les défaut des architectures à mémoire partagée. Sur ces architectures. cela ira très vite. limitées à 16 à 32 processeurs. si l'on peut dire. Le temps d'accès à la mémoire n'est pas uniforme. Là où les systèmes à mémoire partagée rendent l’âme à partir de 16 à 32 processeurs par manque de bande passante mémoire. et tous nos processeurs communiquent entre eux via un ou plusieurs réseaux.siteduzero. En contrepartie. Noeuds Les architectures NUMA les plus simples sont organisées comme les architectures distribuées. Il arrive souvent que l'on préfère regrouper plusieurs processeurs sur la même mémoire. Tout se passe comme si les processeurs étaient reliés à une gigantesque mémoire partagée. bien plus que le système d'exploitation. C'est quoi ? Les architectures NUMA sont similaires aux architectures distribuées : chaque processeur possède sa propre mémoire. et ne doivent pas se compliquer la vie avec des mécanismes de message passing assez complexes à gérer. Mais ces architectures NUMA sont tout de même assez rares. d'autres architectures ont étés inventées. les phénomènes de Bus Contention vus il y a quelques chapitres disparaissent. chaque processeur ayant sa propre mémoire. il faudra aller chercher la donnée dans la mémoire d'un autre processeur en la faisant passer par le réseau d'interconnexion entre processeurs.Partie 4 : Architectures NUMA et distribuées 70/97 NUMA Les architectures distribuées sont assez sympathiques. ce n'est évidemment pas le cas. Mais dans le cas contraire. suivant l'endroit auquel un processeur accède à la fausse mémoire qui rassemble toutes les mémoires de nos processeurs.. Mais contrairement aux architectures distribuées.) : on peut facilement rendre un programme 10 à 100 fois plus rapide en gérant correctement la localité de celui-ci.

siteduzero. certaines architectures NUMA antédiluviennes ne possédaient pas de mécanismes matériels de cohérence des caches. Directory Protocol Les architectures NUMA ont donc besoin de maintenir la cohérence de leurs caches sur un grand nombre de processeur. etc. Dans ces conditions. Cet autre chose. il est évident qu'on ne peut pas utiliser un protocole de cohérence des caches basé sur l'utilisation de ce bus. ces regroupements d'un ou plusieurs processeurs qui sont reliés via un réseau local. ce qui fait que de nos jours toutes les architectures NUMA disposent de mécanismes matériels de cohérence des caches. Autrefois. mais on retombe sur un problème : la cohérence des caches n'est pas assurée. où est la dernière version. ces Directory Protocols sont des protocoles de cohérence des caches dans lesquels un circuit centralise toutes les informations sur nos données partagées : dans quelle cache sont-elles. www. il est placé dans un circuit à part. Ce Directory Protocols est parfois placé en mémoire RAM. Beaucoup de programmeurs ont fini par perdre leur santé mentale. La cohérence devait donc être prise en compte directement au niveau logiciel et c'était au programmeur de gérer celle-ci. Exit donc pour nos Snooping Protocols : on doit trouver autre chose. le retour ! On peut encore une fois utiliser des mémoires caches sur ce genre de machines. mais c'est assez rare : le plus souvent. sont ce qu'on appelle des nœuds . Cohérence des caches. Organisation de base Pour rappel. est-ce que telle ou telle ligne de cache stocke une donnée partagée. Plus personne ne souhaite voir ce genre d'horreur se reproduire. pas forcément reliés à un seul et unique bus.Partie 4 : Architectures NUMA et distribuées 71/97 Chacune de ces unités centrales.com . Ce circuit centralisé s'appelle le directory. ce sont les Directory Protocols.

c'est autre chose : les différents processeurs ne sont pas tous reliés via un seul bus qu'il doivent se partager. Du point de vue de notre directory. est-ce que ce bloc est partagé avec d'utres processeurs . mais sont reliés entre eux via un ou plusieurs réseau locaux. plus le directory sera grand lui aussi. des informations : est-ce que ce bloc de mémoire a été copié dans le cache . le cache est plus petit que la mémoire. on a un directory par nœud. mais utilisent une organisation pair à pair ou centralisée. etc).com . www. un directory n'est pas qu'une mémoire stockant toutes ces informations : il contient aussi des circuits qui décident quoi envoyer sur les réseaux locaux. Invalid. mais cela dépend alors de l'implémentation et du protocole de cohérence de cache utilisé. et des bits de présence. on peut envoyer une information de mise à jour à un processeur en particulier. qui ont chacun leurs avantages et leurs inconvénients. ces informations peuvent être envoyées sans que cela bloque tout le réseau local : les réseaux locaux reliant nos processeurs n'utilisent pas de bus partagé entre tous les processeurs. si oui lesquels . quel est l'état de la ligne de cache contenant ce bloc de mémoire : Shared. On peut remarquer que plus la mémoire centrales est grande. la mémoire est découpée en blocs. Bien sur. et d'autres qui se chargent de mettre à jour les informations du directory. et il peut contenir moins de blocs que la RAM. De plus. Exclusive. sans que tous les autres voient se message. notre directory stocke des informations sur nos données. Bref. de même taille qu'une ligne de cache : chacun de ces blocs de mémoire peut ainsi être chargé dans le cache si le processeur en a besoin. Modified. Ces bits de présence servent à indiquer si la ligne de mémoire a été recopiée dans le cache d'un processeur. Sur les architectures NUMA ces directory protocols ont un sacré avantage comparé aux snooping protocols : ils n'ont pas à envoyer d’informations à tous les autres processeurs lorsqu'une ligne de cache est mise à jour. juste au cas où : même s'ils ne possèdent pas de copie de la donnée qui vient d'être modifiée. etc . Avec les directorys protocols. C'est ainsi : seuls les processeurs possédant une copie de la ligne de cache modifiée vont être prévenus. On va voir ceux-ci dans ce qui suit. Un directory de type Bit Vector contient deux informations principales : l'état du bloc de mémoire (Shared. Sa taille. Directory V oyons un peu plus en détail ce directory. Bien sûr.siteduzero. Bit Vector Le cas le plus simple correspond aux directory de type Bit Vector . pour chacun de ces blocs. et peut-être d'autres inforamtions. notre directory peut aussi contenir d'autres informations pour chaque bloc de mémoire. qui permet ce genre de choses. En conséquence. tous les autres processeurs sont avertis. lorsqu'une ligne de cache est mise à jour sur un processeur. Invalid. Bien sur. Mais la taille du directory et la quantité de circuit qu'il utilise dépend fortement de la taille de la mémoire RAM de son nœud. Chaque processeur dans la machine est associé à un bit de présence : ce bit servira à indiquer si notre bloc de mémoire a été recopié dans le cache de ce processeur. la quantité de mémoire dont il a besoin pour stocker ces données est proportionnelle à la taille de la mémoire RAM du nœud auquel il est attribué. Comme on l'a vu précédemment. Avec les snooping protocols .Partie 4 : Architectures NUMA et distribuées 72/97 Généralement. Le directory va simplement stocker. Owned. il existe différents types de directory.

on stockait des informations sur tous les processeurs. qui pointe vers une liste chainée de nœud. Toute la gestion de cette liste chainée est réalisée par le directory. etc. Un bit de présence peut ainsi être utilisé pour 2. Ceux qui veulent aller plus loin peuvent aller lire ce document : Cache Coherency and Directory Protocols www. alors la lecture de la donnée se fait dans la mémoire RAM du nœud. L'avantage. c'est que dans cette liste. Alors qu'avant. Quand un processeur va lire une donnée dans un nœud. Si celui-ci augmente. par exemple). on peut choisir d'utiliser un bit de présence pour plusieurs processeurs. En tout cas. par le hardware. Pas franchement idéal.siteduzero. Coarse Grained Vector Directory Certains systèmes utilisent une amélioration du Bit Vector.. Si vous êtes intéressés par le sujet.com . Le directory enregistrera alors le fait qu'un autre processeur aura une copie de cette donnée. et on maintient la cohérence des caches entre les processeurs d'un nœud via un snooping protocol . D'autres Il existe bien sur d'autre types de directory plus ou moins complexes. processeurs à la fois. Dynamic Pointer Allocation Dans les directory à base de Dynamic Pointer Allocation . le directory se débrouille pour récupérer la dernière version de la donnée (placée dans le cache) et va l'envoyer au processeur demandeur. 3. Dans ce cas. la quantité de bits de présence augmente un peu trop et le directory fini par faire la taille d'une maison. si vous voulez mon avis. Cette liste chainée est placée soit dans la mémoire RAM du nœud. 4. En cas d'écriture d'une donnée partagée. histoire de garder la cohérence des caches. et devra mémoriser quel était le processeur demandeur de cette copie : il est mis à jour. Le fonctionnement de ce directory est assez simple. Si elle est en mémoire. chaque directory est un peu plus léger. on ne stocke que les processeurs qui possèdent une copie de la donnée.Partie 4 : Architectures NUMA et distribuées 73/97 Ce directory est interrogé à chaque fois qu'un processeur d'un nœud veut aller lire la mémoire d'un autre nœud. il le retire de cette liste chainée. Sinon. ces directorys fonctionnent correctement tant que le nombre de processeurs n'est pas trop grand. quelques recherches Google vous feront le plus grand bien. Il contient juste une adresse mémoire. C'est très utile sur les architectures NUMA qui possèdent plusieurs processeurs par nœuds : on utilise alors un bit de présence par nœud. Et inversement. C'est souvent avantageux car les données sont souvent partagées avec assez peu de processeurs.. et donc notre liste prend assez peu de place. mais je préfère m’arrêter ici. Cette liste chainée de nœud contient tous les processeurs/nœuds qui contiennent une copie du morceau de mémoire. le directory du nœud a juste à ajouter ce processeur dans notre liste chainée. Au lieu d'utiliser un bit de présence par processeur. le directory apparié au nœud qui contient la donnée va vérifier si cette donnée est dans la mémoire cache ou en mémoire. quand un processeur se débarrasse du morceau de mémoire (s'il n'en a plus besoin. soit dans une mémoire intégrée au directory. le directory enverra aux autres directory des informations de mise à jour.

Mais assez de bavardages. les données présentes dans ce paquet seront traitées simultanément. Ces paquets contiennent plusieurs nombres entiers ou nombres flottants placés les uns à coté des autres et regroupés dans ce vecteur. processeurs vectoriels. de nombreux jeux d'instructions spécialisés sont apparus et des processeurs spéciaux ont étés inventés. Mais dans tous les cas. il existe d'autres manières d'exploiter le parallélisme de données au niveau matériel. N'espérez pas commander ce genre d’ordinateurs pour noël ! Comme on va le voir. Ainsi. comme les Thinking machines CM-1 et CM-2. Quand on exécutera une instruction sur un vecteur. rendu 3d. Ces ordinateurs possédaient environ 64000 processeurs minimalistes. commençons ! Instructions SIMD Certains processeurs possèdent des instructions pouvant manipuler plusieurs données simultanément. comme on le voir dans ce qui suit. ce n'est pas le MIMD : c'est le parallélisme de données. Ces données sont rassemblées dans des espèces de blocs de données. Mais pour ce faire. Ainsi. Il existe différents types d'instructions SIMD. V oyons un peu les formes que celui-ci peut prendre : instructions vectorielles. manipulation de sons. vidéo. Le Thread Level Parallelism a des limites assez contraignantes. le SIMD est intégré directement dans le jeu d'instruction du processeur. qui ne peut être efficace et rentable que sur des grosses données. qui est spécialement conçu pour en tirer partie. Et c'est pour cela que ce genre de parallélisme a été le premier à être adopté sur les architectures récentes. Il faut dire que de nombreuses situations s'y prêtes relativement bien : traitement d'image. Traiter des données différentes en parallèle a été la première forme de parallélisme réellement exploitée. qui exécutaient tous la même instruction au même moment. il a quand même fallu concevoir des processeurs et des architectures adaptées. mais chacun sur des données différentes. et seules certaines applications bien spécifiques pourront bénéficier d'un grand nombre de processeurs. L'avenir n'est pas dans un nombre toujours plus grand de processeurs avec une orgie de Threads tous plus parallèles les uns que les autres. celle-ci traitera ces entiers ou flottants indépendamment : au lieu de les traiter unes par unes. ou au nombre de processeurs est vouée à un échec aussi cuisant que le die d'un Pentium 4 sans ventilateur ! Mais la loi de Gustafson est beaucoup plus optimiste. et GPGPU nous attendent. celles-ci travaillent sur un ensemble de données de même taille et de même type. Exemple avec une addition . Comme instructions SIMD.com . on trouve souvent : www. et sur des ordinateurs chers et destinés à des calculs relativement importants. La course au nombre de cœurs. etc. Pour se faire. d'une taille fixe.Partie 4 : Architectures NUMA et distribuées 74/97 Partie 5 : Le parallèlisme de données Comme on l'a vu dans le deuxième chapitre de ce tutoriel. et aborder une autre forme de parallélisme : le parallélisme de données .siteduzero. Streams processors. notre processeur contient souvent une unité de calcul capable de travailler en parallèle sur chaque donnée d'un paquet. Une solution simple est d'utiliser plusieurs processeurs qui exécuteront tous la même instruction. la loi d'Amdhal est sacrément pessimiste. L'avenir. Cette solution a autrefois étés utilisée sur certains supercalculateurs. Mais ce genre de solution est vraiment quelque chose d'assez lourd. le SIMD. Instructions SIMD Nous allons maintenant continuer. qu'on appelle un paquet.

Nos processeurs contiennent des registres spécialisés pouvant chacun contenir un paquet. entier. comme des ET. il est évident que si un résultat prend plus de bits que ce qu'il est censé prendre. Les instructions arithmétiques et quelques autres manipulent des nombres ou des données de taille fixe. Cela a une conséquence : les registres ayant tous une taille fixe. il ne pas être représenté dans notre ordinateur : il se produit ce qu'on appelle un overflow . Pour cela. et éxecuter une ou plusieurs instructions dessus. et le type de celle-si. la taille d'un paquet est fixée une fois pour toute par le jeu d'instruction. des comparaisons . soustractions. ou des images. Il faut donc trouver une solution pour éviter les catastrophe. que se passe-il lors d'un Overflow ? Un Overflow ? Cela mérite peut-être quelques explications. on utilise différentes instructions suivant ce que contient le paquet : l'instruction pour manipuler des paquets contenant des entiers de 16 bits sera différente de celle manipulant des entiers de 32 bits. www. il suffira alors de le charger dans un de ces registres. et il faut parfois effectuer des manipulations spéciales sur nos paquets : des permutations. on devra utiliser deux instructions différente suivant le type des données présentes dans le vecteur (flottant. il vaut mieux éviter qu'il déborde sur la donnée d'à coté. on devra effecteur des manipulations différentes. booléens. multiplications. Pour traiter un paquet. qui ne peuvent prendre leurs valeurs que dans un intervalle déterminé par une valeur minimale et une valeur maximale. des opérations logiques. On devra utiliser des instructions différentes suivant ce qu'on met dans le paquet : suivant la taille des données. Et cela a une certaine incidence sur la façon dont se comportent les instructions dans certaines situations. on utilise donc des instructions supplémentaires. regrouper plusieurs données indépendantes dans un paquet. des OU. 75/97 Mais nos instructions précédentes en suffisent pas. Si le résultat d'un calcul sort de cet intervalle. Par exemple. Et pour cela. ou des opérations de conversion. etc).com . Il faudra aussi gérer la taille des données : il faudra bien savoir comment découper le paquet en données à traiter en parallèle.Partie 5 : Le parallèlisme de données des instructions arithmétiques du style additions. Arithmétique saturée Les instructions SIMD sont très souvent utilisées pour manipuler des données censées représenter de la vidéo. etc. des décalages ou des rotations . Par exemple. voir du son. Cela implique que suivant la taille des données à manipuler.siteduzero. Lorsqu'on travaille avec des paquets. etc . on pourra en placer plus ou moins dans un paquet.

Exemples : MMX. Imaginez que l'on représente une couleur par un entier de 8 bits (ce qui est souvent le cas) : par exemple. Il va de soit que pas mal d'instructions SIMD utilisent cette arithmétique. MM5. et on oubliera le bit en trop. C'est un jeu d'instructions particulièrement ancien. On va donc voir à quoi peuvent bien ressembler ces instructions avec des exemples concrets : on va étudier les instructions des processeurs x86. On peut citer par exemple les extensions MMX. on trouve des instructions arithmétiques. On trouve aussi des soustractions : PSUBB. et cela peut poser des problèmes lorsqu'on manipule de la vidéo. de plus en plus d'instructions ont été ajoutées et rajoutées : ces instructions sont ce qu'on appelle des extensions x86 . apparu certainement avant votre naissance : 1978.. des images ou du son. qui travaillent sur des entiers regroupés dans un paquet occupant un registre MMx. on arrondi le résultat au plus grand entier supporté par le processeur. PADDSW qui fait la même chose avec des paquets d'entiers de 16 bits. le résultats est censé prendre 33 bits. ce qui leur permettait de contenir soit : un entier de 64 bits . par exemple. qui soustrait deux paquets d'entiers de 8 bits . ARM. Ces instructions correspondaient aux instructions qu'on rencontre habituellement dans un jeu d'instruction normal. mais on ne gardera que les 32 bits de base. SSE. PADDSB. je ne mets pas des guillemets par hasard). MM2. je tiens à signaler que les processeurs x86 ne sont pas les seuls à posséder des extensions de leur jeu d'instruction SIMD. MM6 et MM7. Mais il faut bien faire un choix. SPARC. Instructions MMX Le MMX ajoutait aussi pas mal d'instructions SIMD assez basiques. huit entiers de 8 bits. voir 3dnow!. SSE et AVX On a donc vu un peu de théorie sur ces instructions SIMD. Si un résultat est trop grand au point de générer un overflow . du nom de MM0. Par exemple. SSE2. MMX Les premières instructions SIMD furent fournies par une extension x86 du nom de MMX . vu qu'elles sont destinée au traitement d'image ou de son. Ce qui est fort peu crédible ! Et les mêmes problèmes peuvent arriver dans pleins d'autres situations : il vaut mieux qu'un son trop fort sature au lieu de le transformer en un son trop faible. www. MM3. Ce MMX a été introduit par Intel dans les années 1996 sur le processeur Pentium MMX et a perduré durant quelques années avant d'être remplacé par les extensions SSE. On trouve ainsi des instructions d'addition et de soustraction : PADDB. En ajoutant deux couleurs très proches du blanc (et donc deux entiers assez grands). et j'ai personnellement décidé de voir les différentes instructions SIMD des processeurs x86. MM4.. PADDD. Depuis. Les processeurs PowerPC. ont eux aussi des instructions SIMD dignes de ce nom et qui mériteraient toutes un détour.com . qui additionne deux paquets d'entiers de 32 bits . une opération sur deux nombres très grands peut donner un nombre très petit. Le MMX ne manipulait que des nombres entiers : pas de nombres flottants à l'horizon. le résultat obtenu sera un entier assez petit. PADDW. qui prend un seul paquet et additionne tous les entiers de 8 bits contenus dans ce paquet . quatre entiers de 16 bits . MM1. qui additionne deux paquets d'entiers de 8 bits . le blanc correspondra à la valeur maximal de cet entier. Ainsi. mais un peu de pratique ne ferait surement pas de mal.siteduzero. deux entiers de 32 bits . Le jeu d'instruction de nos PC qui fonctionnent sous Windows est appelé le x86. qui additionne deux paquets d'entiers de 16 bits . Une grande quantité de ces extensions x86 sont des ajouts d'instructions SIMD. registres MMX Ce MMX introduisait 8 "nouveaux registres" (vous verrez. Seul problème : en faisant cela. Avant toute chose. et le noir à 0. MIPS et bien d'autres. Mais il existe une solution : utiliser ce qu'on appelle l'arithmétique saturée. Ceux-ci avaient une taille de 64 bits. si on additionne deux nombres de 32 bits. présents dans nos PC. qui sera donc proche du noir.Partie 5 : Le parallèlisme de données 76/97 Première solution : on ne conserve que les bits de poids faibles du résultat et les bits en trop sont simplement oubliés.

ainsi qu'à un registre d'état de 32 bits. qui comparent deux paquets d'entiers de 16 bits . tandis que les instructions commençant par PCMPGT testent si la seconde opérande est supérieure à la première.Partie 5 : Le parallèlisme de données PSUBW. qui comparent deux paquets d'entiers de 8 bits . 32 et 64 bits . PCMPEQW et PCMPGTW. aux noms assez explicites . je suppose que vous avez deviné que le MMX avait quelques problèmes. PSLRW. et l'instruction PANDN.com . Ces opérations arithmétiques utilisent l’arithmétique saturée. C'est Intel qui a inventé la notion d'EPIC FAIL ! Vu le titre. POR et PXOR. soit 0 dans le cas contraire. Les instructions commençant par PCMPEQ testent d'égalité des entiers du paquet. Mais si on regarde bien. on voit bien que l'absence de registre d'état pose quelques problèmes. PCMPEQD et PCMPGTD. 32 bits. PSLLQ : décalage logique à gauche d'entiers de 16. capable d’effectuer des opérations sur les flottants. qui comparent deux paquets d'entiers de 32 bits. PSLLD. A ces instructions. Pire : les instructions MMX ne mettent aucun registre d'état à jour : ni celui des entiers. Là encore. qui soustrait deux paquets d'entiers de 32 bits . et pareil pour les calculs sur les flottants. En plus de ces opérations. et elles ne préviennent pas en cas d'overflow ou d'underflow si ceux-ci arrivent (pour les instructions qui ne travaillent pas en arithmétique saturée). Ces instructions ne manipulent pas le registre d'état. leur résultat est fourni dans un paquet de 64 bits qui contient pour chaque entier : soit la valeur maximale si la condition testée est vraie. 77/97 Petit détail. PSRAD : décalage arithmétique à droite d'entiers de 16. et n'est pas enregistré dans un registre. PSLRQ : décalage logique à droite d'entiers de 16. En conséquence. etc. mais avec quelques petites subtilités. qui met à zéro un registre MMx. Et enfin. Par exemple. Tous les processeurs x86 capables de manipuler des flottants comportent une unité de calcul flottante (une FPU) de base. L'ensemble des instructions machines supportées par cette unité s'appelle le jeu d'instruction x87 . on peut rajouter des instructions de conversion et de copie entre registres MMx et la mémoire. via l'instruction PMULLW. Sur les processeurs x86. qui stockent des bits permettant d'avoir quelques informations sur l'état du processeur ou le résultat d'une instruction. PSRAW. on trouve un ou plusieurs registre spéciaux. Cette FPU x87 est associée à 8 registres flottants de 80 bits. les registres d'état. qui soustrait deux paquets d'entiers de 16 bits .siteduzero. dans la majorité des processeurs. PSUBD. le résultat d'une comparaison est stocké dans certains du registre d'état. Et bien c'est le cas : il y avait un léger problème au niveau des registres MMx. on n'a pas de registre d'état pour les instructions MMX. un des bits du registre d'état sera mit à 1. Cela contraste fortement au fonctionnement des instructions normales : avec des instructions x86 habituelle. La multiplication est aussi supportée. on peut ajouter les instructions de décalages et de rotations : PSLLW. Il existe aussi d'autres bits qui servent à stocker des informations du style : le résultat de l'instruction est un zéro. si le résultat d'un calcul déborde et qu'un overflow a lieu. Et c'est là qu'est le problème : chaque registres MMX correspondait aux 64 bits de poids faible d'un des 8 registres flottants de la x87 ! www. 32 et 64 bits . on retrouve aussi des opérations logiques : PAND. On trouve aussi des instructions de comparaisons : PCMPEQB et PCMPGTB. ni celui des flottants. PSLRD. les calculs sur les nombres entiers ont leur registre d'état bien à eux. cette FPU est couramment appelée la FPU x87 .

Chose étrange. Mit à 1 si overflow . Cela permet d’accélérer les calculs : il faut dire que la gestion des dénormaux est assez compliquée et que ces flottants dénormalisés ne sont pas forcément supportés par certains processeurs. Le pire. seuls les 16 premiers bits de ce registres ont une utilité : les concepteurs du SSE ont surement préférés laisser un peu de marge au cas où. notre processeur va générer ce qu'on appelle une exception matérielle : le processeur va interrompre temporairement l’exécution du programme en cours et va exécuter un petit programme capable de traiter l'exception. SSE4. Mit à 1 si une division par zéro a eu lieu. SSE3.siteduzero. Cette extension fit apparaitre 8 nouveaux registres. etc. En faisant ainsi. et est possible pour les nombres flottants. Sur les processeurs 64 bits. Cela a sacrément gêné les programmeurs ou les compilateurs qui voulaient utiliser le jeu d'instruction MMX. c'est que ce n'était pas un bug. Si il est configuré à 1. celle-ci reste malgré tout très lente. Ce SSE fut ensuite complété. L'underflow est l'inverse de l'overflow . une nouvelle extension SIMD fit son apparition sur les processeurs Intel Pentium 3 : le Streaming SIMD Extensions. Si un calcul sur un nombre flottant renvoie un résultat trop petit pour tenir dans un nombre flottants. ces registres sont doublés et on en trouve donc 16. Mit à 1 si une instruction flottante est obligé d'arrondir son résultat pour le faire renter dans un nombre flottant. Bit Utilité Mit à 1 si une opération renvoie un résultat invalide.Partie 5 : Le parallèlisme de données 78/97 En clair : il était impossible d'utiliser en même temps l'unité de calcul flottante et l'unité MMX. Ces dénormaux sont des nombres flottants spéciaux qu'on utilise pour représenter des très petits nombres. les registres XMM. Dans notre cas. Mit à 1 si underflow . IE DE ZE OE UE PE DAZ www. il est arrondi à 0 et on se retrouve avec un underflow . il n'y avait pas à sauvegarder 8 registres supplémentaires lorsqu'on appelait une fonction ou qu'on devait interrompre un programme pour en lancer un autre (changement de contexte. Mit à 1 si le résultat de l'instruction est un flottant dénormal. mais quelque chose de réellement voulu par Intel. Ce registre n'est autre que le registre MXCSR. interruption. SSE Dans les années 1999. on trouve aussi un nouveau registre supplémentaire qui permet de contrôler le comportement des instructions SSE : celui contient des bits qui permettront de dire au processeur que les instructions doivent arrondir leurs calculs d'une certaine façon. abrévié SSE. tous les nombres flottants dénormaux sont considérés comme des zéros dans les calculs. En plus de ces registres. ce programme va émuler logiciellement le traitement de ces dénormaux. etc. Exemple avec les registres XMM des processeurs x86 . Mais même lorsque la gestion des dénormaux est implémentée en hardware (comme c'est le cas sur certains processeurs AMD). etc) : la sauvegarde des registres de la FPU x87 suffisait. et différentes versions virent le jour : le SSE2. Si le résultat d'un calcul est un nombre dénormalisé.com .

Ce qui commençait à faire beaucoup. les instructions MOVNTQ et MOVNTPS du SSE premiére version. et on arrondit le résultat. Si à 1. et plus intéressant : les deux instructions MWAIT et MONITOR qui permettent de paralléliser plus facilement nos programmes. avec ses 13 instructions supplémentaires. Si 1. opérations arithmétiques. je ne vais pas toutes les lister. Servent à indiquer comment il faut arrondir un résultat qui ne rentre pas dans un nombre flottant: vers zéro. Le SSE2 incorporait ainsi pas moins de 144 instructions différentes. SSE La première version du SSE contenait assez peu d'instructions : seulement 70. Petit détail : la multiplication est gérée plus simplement. on ne lève pas d'exception en cas d'underflow . On trouve aussi des instructions permettant de charger le contenu d'une portion de mémoire dans le cache. Croyez-moi. etc). des opérations arithmétiques supplémentaires. On peut quand même signaler une chose : des instructions permettant de contrôler le cache firent leur apparition. des instructions pour les nombres complexes. Autant vous dire qu'utiliser ces instructions peut donner lieu à de sacrés gains si on s'y prend correctement ! Il faut tout de même noter que le SSE n'est pas seul "jeu d'instruction" incorporant des instructions de contrôle du cache : certains jeux d'instruction POWER PC (je pense à l'Altivec) ont aussi cette particularité. on ne lève pas d'exception en cas de division par zéro. et pas mal d'instructions pour charger des données depuis la mémoire dans un registre. Le SSE première version ne fournissait que des instructions pouvant manipuler des paquets contenant 4 nombres flottants de 32 bits (simple précision). de nouvelles instructions furent ajoutés. et l'on a pas besoin de s’embêter à faire mumuse avec plusieurs instructions différentes pour faire une simple multiplication comme avec le MMX. le processeur ne lève pas d'exception si une instruction fourni un résultat dénormalisé. Si à 1. ou vers le flottant le plus proche. PREFETCH2 et PREFETCHNTA. mais je peux quand-même dire qu'on trouve des instructions similaires au MMX (comparaisons. instructions du SSE première version incluses. rotations. 16 entiers de 8 bits. 4 entiers de 64 bits . PREFETCH1. Si à 1. vient le SSE3. www. vers . Pas grand chose à signaler. on traite les underflow en arrondissant le résultat à zéro. décalages. On peut citer par exemple. SSE2 Avec le SSE2. SSE3 Puis. Si configuré à 1. le processeur le lève pas d'exception matérielle en cas d’exécution d'une opération invalide.com .siteduzero. . On y trouvait notamment des permutations. De telles instructions de prefetch permettent ainsi de charger à l'avance une donnée dont on aura besoin. Ces instructions permettent ainsi de garder le cache propre en évitant de copier inutilement des données dedans. si ce n'est que des instructions permettant d'additionner ou de soustraire tout les éléments d'un paquet SSE ensemble. 8 entiers de 16 bits . Si à 1. et l'on peut maintenant utiliser en plus des paquets contenant : 2 nombres flottants 64 bits (double précision) . on ne lève pas d'exception en cas d'overflow . avec pas mal d'opérations en plus. ce qui permet de contrôler son contenu. opérations logiques. on ne lève pas d'exception quand une instruction flottante arrondit son résultat. permettant de supprimer pas mal de cache miss. On retrouve ainsi des instructions qui permet d'écrire ou de lire le contenu d'un registre XMM en mémoire sans le copier dans le cache. Le SSE fournissait notamment les instruction PREFETCH0. 2 entiers de 64 bits .Partie 5 : Le parallèlisme de données 79/97 IM DM ZM OM UM PM Bits 13 à 14 FZ Si configuré à 1.

L'AVX complète le SSE et ses extensions. et que les instructions AVX permettent de préciser le registre de destination en plus des registres stockant les opérandes. AVX Enfin. de calcul de produits scalaire. le résultat d'une instruction SIMD était écrit dans un des deux registres d'opérande manipulé par l'instruction : il fallait donc sauvegarder son contenu si on en avait besoin plus tard. ce n'est plus le cas : on peut se passer des opérations de sauvegarde sans problème. www. et surtout en permettant de traiter des données de 256 bits. Avec celle-ci. de calcul du minimum ou du maximum de deux entiers. Ces registres YMM sont partagés avec les registres XMM : les 128 bits de poids faible des registres YMM ne sont autre que les registres XMM.1 introduit ainsi des opérations de calcul de moyenne. Avec le SSE et le MMX. on retrouve 16 registres nommés de YMM0 à YMM15. le vice à été poussé jusqu'à incorporer des instructions de traitement de chaines de caractères. Avec le SSE4. ce qui supprime pas mal d'instructions.com . Avec l'AVX.siteduzero. et quelques autres. dédiés aux instructions AVX et d'une taille de 256 bits. la dernière extension en date est l'AVX. en rajoutant quelques instructions.Partie 5 : Le parallèlisme de données 80/97 SSE4 Le SSE4 fut un peu plus complexe et fut décliné lui-même en 2 sous-versions. de copie conditionnelle de registre (un registre est copié dans un autre si le résultat d'une opération de comparaison précédente est vrai). Son principal atout face au SSE. Le SSE4. des calculs d'arrondis.2.

Partie 5 : Le parallèlisme de données 81/97 Les processeurs vectoriels Avec nos instructions SIMD. suivant le processeur. l'unité scalaire peut aussi avoir des registres dédiés. Ces données devaient impérativement être placées les unes après les autres dans la mémoire. mais c'est un détail. Processeur vectoriels Memory-Memory Nous allons commencer par aborder les processeurs vectoriels qui travaillent directement en mémoire. Ces processeurs sont ce qu'on appelle des processeurs vectoriels . qui gère les instructions qui ne sont pas vectorielles. Ces registres vectoriels ne sont rien d'autres que ces registres pouvant contenir des vecteurs. chargé de gérer les instructions vectorielles. ou des morceaux de vecteur d'une taille fixe (128. Il faut savoir que le fait qu'ils accèdent directement à la mémoire a quelques conséquences sur leurs architectures. Architectures Il existe deux grands types de processeurs vectoriels : les processeurs vectoriels Memory-Memory . et certains processeurs assez anciens allaient un peu plus loin. et les processeurs vectoriels Load-Store. Il contient aussi une unité de calcul normale. qui avaient la taille d'un registre. Bizarrement. 512 bits). qui travaillent avec des registres vectoriels. nous allons voir une autre forme d'architecture : les processeurs vectoriels de type Load-Store.siteduzero.com . et avaient quelques limitations : on était obligé de traiter tous les éléments stockés dans un paquet. 256. ces techniques sont assez rudimentaires. www. on pouvait travailler sur des paquets de taille fixe. Un processeur vectoriel contient souvent un circuit à part. C'est la même chose que les registres qui stockaient des paquets sur les processeurs utilisant des instructions SIMD. Processeurs vectoriels Load-Store Maintenant. quelque soit la situation. et que ces processeurs possèdent quelques spécificités. L'architecture d'un processeur vectoriel MemoryMemory ressemble donc à ceci : Alors certes. qu'on appelle aussi les processeurs vectoriels Memory-Memory.

ces deux étapes sont complètement indépendantes et utilisent des circuits séparés. Ce n'est pas le cas des processeurs vectoriels. et de refaire pareil. une étape M qui va multiplier les mantisses . on peut avoir 3 étapes : une première étape E qui va additionner les exposants et gérer les diverses exceptions . on peut charger la prochaine instruction pendant qu'on exécute la précédente : c'est un peu ce qu'on a vu dans le chapitre sur la parallélisme à un seul processeur. il y a une différence. Avec les instructions SIMD. qui font autrement : ils travaillent à la chaine. Et rien n’empêche de découper les étapes de chargement d'une instruction et leur exécution par l'unité de calcul en plusieurs étapes bien distinctes. C'est très différent des instructions SIMD qui manipulaient des élèvements indépendants dans des unités de calculs séparées. On dispose d'une grosse unité de calcul qui peut effectuer certaines instructions. en mémoire RAM à chaque fois qu'on veut les réutiliser. on peut ainsi commencer le traitement d'une nouvelle donnée sans avoir à attendre que l'ancienne soit terminée.Partie 5 : Le parallèlisme de données 82/97 Avoir de tels registres est assez avantageux : on peut réutiliser des données placées dans ce registres au lieu de devoir aller lire nos paquets. Son exécution peut être décomposée en plusieurs étapes. Pipelining La différence tient dans la façon dont sont traités nos paquets. on ne voit pas vraiment la différence entre les processeurs vectoriels de type Load-Store et les processeurs dotés d'instructions SIMD. Jusque ici. www. Cela ressemble à un peu au fonctionnement d'une chaine de montage. Mais comme vous allez le voir. Par exemple. dans laquelle on découpe la fabrication d'un objet en pleins de sous-étapes qu'on effectue les unes après les autres dans des boxes différents. et qui traite tous les éléments d'un paquet simultanément. si ce n'est les unités de calcul vectorielles séparées. Après tout.com . Au lieu d'attendre que l’exécution d'un opération sur une donnée soit terminée avant de passer à la suivante.siteduzero. Par exemple. C'est pour cela que les architectures vectorielles sont presque toutes des architectures vectorielles de type Load-Store. nos vecteurs. on peut donner l'exemple d'une multiplication flottante effectuée entre deux registres. dans une seule grosse unité de calcul. ou avec des unités de calcul séparés ! Ces processeurs pipelinent leurs unités de calcul vectorielles ! Par pipeliner. Pour donner un exemple. indépendantes les unes des autres. on traitait chaque élément séparément. nos vecteurs. on veut dire que l’exécution de chaque instruction sera découpée en plusieurs étapes.

le fait qu'elle soit pipelinée va avoir de drôles de conséquences : une instruction vectorielle met un certain temps avant d'être vraiment efficace. j’exécuterais un nombre de plus en plus important d’éléments en même temps. et donc dans des circuits séparés.com . Puis. mais dans des étapes différentes.Partie 5 : Le parallèlisme de données et enfin une étape A qui va arrondir le résultat. on va reprendre l'exemple de notre multiplication flottante vu au-dessus. Supposons que je travaille sur un vecteur de 8 nombres flottants. Au départ. dans un paquet SIMD. j'aurais fini l'étape 1 du premier élément. je commencerais à éxecuter la première étape sur le premier élément : je ne traiterais alors que mon premier élément. qui passera à l’étape 2. au cycle d'horloge suivant. L’exécution de notre opération flottante sur un vecteur donnerait donc quelque chose dans le genre : 83/97 Chaque ligne correspond au traitement d'un nouvel élément dans un vecteur. Startup et Dead Time Quand une instruction vectorielle s’exécute. Pour expliquer pourquoi. Je traiterais alors deux éléments en même temps. En même temps.siteduzero. et seulement celui-ci. www. Au fur et à mesure. et je finirais alors par avoir chaque circuit dédié à une étape occupés à traiter un élément : je serais alors en régime de crête. je commencerais l'étape 1 du second élément du vecteur.

siteduzero.com . Ils peuvent démarrer une nouvelle instruction vectorielle sans attendre la fin d'une instruction précédente. sont inutilisés. On peut ainsi pipeliner les traitements à l'intérieur d'une instruction vectorielle.Partie 5 : Le parallèlisme de données 84/97 Tout se passe comme si je mettais un certain temps avant d'arriver à mon régime de croisière. durant lesquels une bonne partie des étapes. et moins on effectue d'étape. mais aussi entre deux opérations vectorielles. Plus on doit traiter d’éléments dans notre vecteur (plus celui-ci est long). www. et du nombre d’éléments à traiter. un certain nombre de cycles d'horloges pour que toutes mes étapes soient totalement utilisées. certains processeurs vectoriels utilisent la ruse. Ce temps de démarrage est strictement égal du nombre d'étapes nécessaires pour effectuer une instruction. Elle dépend fortement du nombre d'étapes de notre instruction. la durée du régime de croisière n'est pas énorme. plus on atteint et quitte le régime optimal rapidement. Il me faut donc attendre un certain temps. quand il ne reste plus suffisamment d’éléments à traiter pour remplir toutes les étapes. Durant un certain temps. je n'ai pas commencé à traiter suffisamment d’éléments pour que toutes mes étapes soient occupées. La même chose arrive vers la fin du vecteur. Elles peuvent ainsi se chevaucher pour remplir les vides. des circuits de notre ALU. Comme on le voit. Solutions Pour amortir ces temps de démarrage et de fin.

On pourrait penser que l'on doit effecteur d'abord la multiplication des paquets B et C. Notre processeur peut en effet fusionner ces deux instructions indépendantes et les traiter en interne comme s'il s’agissait d'une instruction unique. Ainsi. Il s'agit de ce qu'on appelle le Vector Chaining . le résultat de notre multiplication doit être réutilisable sans attendre par notre unité de calcul une fois qu'il est calculée : on ne doit pas avoir besoin de l'enregistrer en mémoire pour pouvoir calculer avec. chaque instruction étant à une étape différente.En gros. Pour cela. Dans ce cas. je dois utiliser deux instruction vectorielles : une d'addition. il fusionne plusieurs instructions vectorielles en une seule instruction vectorielle qui regroupe les deux. et effectuer l'addition de ce tableau avec le paquet A. on s’aperçoit qu'on peut simplifier le tout et utiliser notre pipeline plus correctement. je souhaite effectuer le calcul .. il faut savoir que les données manipulées par nos instructions vectorielles ne passent pas par la mémoire cache. Il faut dire que la mémoire cache est utile lorsque les données qu'on va placer dedans sont réutilisées plusieurs fois. Il arrive aussi que nos processeurs vectoriels ne possèdent souvent aucune mémoire cache. stocker le résultat dans un paquet temporaire. ou très peu. chacun de ces éléments allant dans une ALU. Chaining Cette technique du pipeline peut encore être améliorée dans certains cas particuliers. puis l'addition séparément pour chaque élément du paquet. Au lieu d'effectuer la multiplication.. mais les accès www. Multiple Lanes Cependant. il faut absolument que le nombre d'étapes d'une instruction soit suffisamment petit. on doit modifier notre pipeline de façon à ce que le résultat de chaque étape d'un calcul soit réutilisable au cycle d’horloge suivant. il peut effectuer la multiplication et l'addition pour le premier élément. B et C. les deux calculs sont exécutés l'un après l’autre. Vu que nos ALU sont pipelinées. pour éviter les problèmes lorsque deux instructions vectorielles qui se suivent manipulent le même vecteur. Pour ce faire. Mon processeur ne disposant pas d'instruction permettant de faire en une fois ce calcul. avant de passer à l’élément suivant. etc. ils sont capables de traiter plusieurs éléments d'un vecteur en une fois. Mais quoiqu'il en soit. ça envoie du bois ! Accès mémoires La façon dont ces instructions vectorielles accèdent à la mémoire est assez intéressante. il faut avoir des vecteurs suffisamment qui contiennent plus d’éléments qu'il n'y a d'étapes pour effectuer notre instruction.siteduzero. la donnée est chargée dans le cache depuis la RAM. on doit préciser que certains de nos processeurs vectoriels peuvent utiliser plusieurs ALU pour accélérer leur temps de calcul. rien n’empêche d’exécuter plusieurs instructions vectorielles différentes dans une seule ALU. puis continuer avec le second. Imaginons que l'on aie trois paquets : A. Il faut que la première instruction aie fini de manipuler un élément avant que la suivante ne veuille commencer à le modifier.com . et est ensuite accédée dans le cache : le premier accès est aussi lent qu'un accès mémoire normal.Partie 5 : Le parallèlisme de données 85/97 Par contre. et une autre de multiplication. Pour chaque iéme élément de ces paquets. Mais en rusant un peu. Mais dans notre processeur vectoriel. dans l'exemple du dessus. En clair. Et quand le tout est couplé aux techniques de Chaining .

Patterns d'accès mémoire Nos instructions vectorielles ont des fonctionnalités assez intéressantes : elles peuvent fournir au processeur des informations sur la façon dont le processeur doit accéder à la mémoire. qui peut monter à plusieurs centaines d'accès mémoires. nos instructions vectorielles peuvent se passer de mémoires caches. Pipelining des accès mémoires Reste que nos accès mémoires sont un problème : ils sont lents. nos accès mémoires se "recouvrent".Partie 5 : Le parallèlisme de données 86/97 suivants vont aller lire ou écrire dans la mémoire cache.siteduzero. et encore : chaque élément doit être traité indépendamment des autres. les processeurs vectoriels supportent souvent un grand nombre d'accès mémoires simultané. les accès mémoires sont pipelinés : cela signifie qu'on pet commencer un accès mémoire sans attendre que le précédent soit fini. ces instructions vont accéder à des données placées les unes à coté des autres en mémoire. notre processeur serait bien ennuyé. nos processeurs vectoriels disposent de diverses astuces. Notre mémoire cache est alors inutile : le seul accès fait sur notre donnée sera aussi lent avec ou sans cache. Accès contigus Généralement. bien plus rapide que la RAM. ce n'est pas la seule astuce. la donnée n'est accédée qu'une seule fois : elles servent souvent à itérer dans un tableau.com . L'instruction a juste à dire au processeur de toujours charger la donnée immédiatement suivante. En clair. L'exemple du dessus n'est pas à l'échelle : en réalité. Mais dans le cas qui nous intéresse. Ce qui fait qu'en général. Déjà. Avec aussi peu de réutilisation des données. Cela lui permet de savoir la position en mémoire (l'adresse) de la prochaine donnée à charger : sans ça. et on peut en éxecuter plusieurs en même temps : c'est plus rapide que de les effectuer un par uns. et on l'oublie pour un moment. et le nombre d'accès mémoire simultanés doit être beaucoup plus grand si on veut garder des performances correctes. à savoir l'utilisation majeure qui est faite des instructions vectorielle. Ainsi. les accès mémoires durent plus longtemps. Mais comme on va le voir. on n'accède qu'une seule fois à un élément de notre tableau. Pour limiter la casse. www.

C'est une des grosses différences entre les processeurs vectoriels et les instructions SIMD qu'on a vu auparavant : ces dernières ne gèrent pas les accès mémoires complexes (comme ceux en strides). qui contiennent généralement 8 bits (un octet). on peut faire en sorte qu'avec des strides assez longs. On se retrouve alors dans la même situation qu'avec les mémoires interleaved : on peut commencer à aller chercher l’élément suivant alors que le précédent n'est www. Cela permet de limiter le temps mit pour accéder à notre byte. Le nom fait peur. Cela pouvait poser quelques problèmes : si jamais on ne veux traiter que certains éléments. L'accès à un boitier prend toujours un peu de temps : c'est le fameux temps d'accès. Par exemple. Sur les mémoires interleaved . Une mémoire interleaved est une mémoire spécialement conçue pour que les accès à des données consécutives soit le plus rapide possible. Si vous ne le savez pas encore. mais il sera moins impressionnant une fois que vous aurez lus les explications qui suivent.com . les bytes dont les adresses mémoires sont consécutives sont placés dans des sous-mémoires consécutives. la donne est différentes : des bytes consécutifs sont localisés dans des boitiers différentes qui peuvent être accédés en parallèle. et qu'on souhaite lire/écrire ces deux bytes.siteduzero. Pour pouvoir identifier ces blocs de mémoires. cela signifie placer plusieurs mémoires séparées et les rassembler dans une seule grosse mémoire. la mémoire de l'ordinateur doit être conçue pour. on devra attendre que l'accès au premier byte soit fini avant de pouvoir accéder au suivant (sauf si la mémoire est multiports. Généralement. accessibles séparément. Dans le cas d'accès mémoires pas vraiment consécutifs en mémoire et plutôt éloignés. Les processeurs vectoriels n'ont pas ce genre de problèmes. on leur attribue à chacun un numéro qui permet de les identifier : leur adresse mémoire. on peut en charger 64 d'un coup sans aucun problèmes. Si on place deux bytes ayant des adresses consécutives dans le même boitier. En utilisant des bancs mémoires de bonne taille. sans instructionner sur l'axe des y ou des z. sachez que notre mémoire est découpée en petits blocs de mémoire. De tels accès sont ce qu'on appelle des accès en stride. on peut très bien ne vouloir traiter que les coordonnées sur l'axe des x. et se contentent d’accéder à des données contiguës. Mais attention : cela ne marche qu'avec des accès mémoires consécutifs. On peut ainsi charger plusieurs mots contigus en mémoires séparément : au lieu de charger notre donnée bit par bit. cela signifie qu'ils seront assez distants dans la mémoire. le x étant souvent fourni via l'instruction (il est incorporé dans la suite de de bits de l'instruction). L'instruction a juste à dire au processeur de toujours charger la donnée située x cases plus loin. on ne pouvait pas utiliser d'instructions SIMD. ces petites sous-mémoires séparées sont appelées des bancs mémoires . ces bytes. je l'avoue. En clair : on ne peut effectuer qu'un seul accès à la fois sur des bytes consécutifs. Accès en strides Il arrive aussi assez souvent que l'on accède qu'à certains éléments tous séparés par une même distance.Partie 5 : Le parallèlisme de données 87/97 Pour améliorer les performances lors de tels accès à la mémoire. Généralement. on doit éxecuter nos accès mémoires uns par uns. si on fait des calculs de géométrie dans l'espace. Elle est en réalité constituée de plusieurs mémoires assemblées dans une seule et unique barrette d'une certaine façon. Vu que nos accès en stride sont souvent assez éloignés. Pour gérer efficacement ces accès en Stride. les éléments à accéder soient placés dans des bancs séparés. mais bref). Mais ce n'est valable qu'avec les mémoires qui ne sont pas des mémoires interleaved ! Avec les mémoires interleaved . nos processeurs vectoriels sont souvent associés à des mémoires interleaved .

le scatter-gather ! Oui. même si elles gagneraient à être reprises sur celles-ci.Partie 5 : Le parallèlisme de données pas encore disponible. www. on va fournir à notre processeur un paquet d'index qui stockera les adresses mémoires des données dispersées à rassembler dans un paquet. Pour comprendre ce que peut être cet accès mémoire. Vector Length Register Pour comprendre ces techniques. Dans un programme. Quand une instruction exécutera un accès en scatter. Alors certes. Ce genre d'accès permet ainsi d'utiliser des instructions SIMD ou des instructions vectorielles plus souvent. il faut savoir certaines choses. déterminée par l'architecture de la machine. il existe aussi l’inverse : on peut écrire les données d'un paquet dans des emplacements mémoires dispersés. on est obligé de répéter notre instruction à appliquer au tableau plusieurs fois. Dans ce cas. mais c'est toujours ça de pris ! Quelques registres utiles Nos processeurs vectoriels utilisent donc des paquets. les bytes de ces adresses seront rapatriées depuis la mémoire et rassemblées dans un paquet. Ces tableaux ne sont rien d'autre qu'un rassemblement de données de même type (et donc de même taille) placées les unes à la suite des autres en mémoire. difficile d'utiliser des instructions vectorielles. Les accès en scatter et en gather cherchent à résoudre ce genre de problèmes en rassemblant des données dispersées en mémoire dans un registre censée contenir des paquets. Et dans ce cas. des vecteurs de taille fixe. il faut savoir que nos paquets SIMD et autres vecteurs de machines vectorielles sont utilisés pour manipuler des tableaux de façon efficiente. Mais nos processeurs vectoriels incorporent diverses techniques que de simples instructions SIMD n'ont pas forcément. j'aurais du mal à le traiter intégralement en utilisant des instructions vectorielles ou des instructions SIMD qui ne peuvent gérer que 64 éléments. Bien sûr. on ne gagne pas beaucoup. 88/97 Accès en Scatter/gather Les processeurs vectoriels incorporent aussi un autre type d'accès à la mémoire. sur des morceaux de tableaux différents (on utilise une simple boucle). C'est exactement la même chose que pour les instructions SIMD.com . le nom est barbare et il faudra vous y faire. Mais ces tableaux n'ont pas forcément la même taille qu'un paquet SIMD. et donc de gagner en performances.siteduzero. Si j'ai un tableau de 1000 éléments. Pour cela. on doit parfois accéder à des données qui sont assez dispersées dans la mémoire.

Ce Vector Mask Register va stocker des bits pour chaque flottant présent dans le vecteur à traiter. V ous remarquerez que 1000 / 64.siteduzero. notre instruction ne doit pas la modifier. www. Du moins. Mine de rien. et il reste quelques éléments qu'on doit traiter. avec une instruction vectorielle. Vector Mask Register Ces instructions SIMD sont très utiles quand on doit effectuer un traitement identique sur un ensemble de données identiques. pas sans aide. notre instruction doit s’exécuter sur la donnée associée à ce bit. Si ce bit est à 1. et il restera 40 éléments à traiter. On peut ainsi traiter seulement une partie des registres stockant des vecteurs SIMD. sauf quand on arrive à la fin d'un tableau : on configure alors notre Vector Length Register pour traiter seulement ce qui reste. On peut ainsi dire au processeur : je veux que tu ne traite que les 40 premiers éléments présents d'un paquet. ça ne tombe pas juste : si on découpe notre tableau. imaginons que je veuille seulement additionner les éléments d'un paquet ensemble s'ils sont positifs : je ne peux pas le faire avec une instruction vectorielle "normale". Reprenons notre exemple avec un tableau de 1000 éléments. C'est ainsi : la taille d'un tableau n'est pas forcément multiple de la taille d'un paquet SIMD ou d'un vecteur. Celui-ci permet de stocker des informations qui permettront de sélectionner certaines données et pas d'autres pour faire notre calcul. on est obligé de calculer et de modifier tous les éléments d'un paquet : il est impossible de zapper certains éléments d'un paquet dans certaines conditions. certaines processeurs vectoriels dignes de ce nom utilisent un Vector Mask Register. soit on demande de l'aide au processeur.com . Sinon. Comment faire pour traiter ces éléments restants ? Dans ce cas. Généralement notre instruction va traiter tout les éléments d'un vecteur. les choses se gâtent. et c'est au compilateur de gérer le tout (via des techniques de Strip Mining ). Mais dès que le traitement à effectuer sur nos données varie suivant le résultat d'un test ou d'un branchement. et des paquets de 64 éléments. on se retrouvera avec 15 paquets complets de 64 éléments. et que tu oublie les autres. Notre processeur peut fournir un Vector Length Register qui indique combien d’éléments on doit traiter dans notre vecteur. deux solutions : soit on les traite en utilisant l'unité de calcul normale. Pour résoudre ce problèmes. Par exemple.Partie 5 : Le parallèlisme de données 89/97 Cela peut poser quelques problèmes.

Et ces derniers n'étaient pas identiques : les instructions qu'ils étaient capables d'effectuer n'étaient pas les mêmes suivant que ces processeurs traitaient de la géométrie ou des pixels. V oyons un peu ce que nos GPU ont dans le ventre. capables d’exécuter des instructions sur des pixels ou des données géométriques. Ces unités de traitement n'étaient ni plus ni moins que de processeurs assez rudimentaires. De nos jours. on pouvait créer des Shaders. capables d'effectuer des instructions entières et flottantes. ces GPU sont devenus de plus en plus programmables. Aussi. Ces derniers sont appelés des Graphic Processing Unit. des petits programmes permettant de manipuler des pixels ou des données géométriques (des Vertex). l'idée d'utiliser ces processeurs pour autre chose que des traitement graphique fit son chemin. Avec le temps. Ces processeurs furent alors un peu modifiés. on a parlé des instructions SIMD. Pour traiter ces Shaders. Architecture d'un GPU Mais c'est quoi le rapport entre les cartes graphiques et le parallélisme ? Et bien les cartes graphiques intègrent des processeurs spécialisés dont l'architecture est fortement parallèle. Stream Processors De nos jours. Ces dernières années. Ces deux types de processeurs sont particulièrement adaptés pour traiter des données en parallèle.Partie 5 : Le parallèlisme de données 90/97 GPGPU et Streams Processors Dans les chapitres précédents. et des processeurs vectoriels. notre carte graphique incorporait des unités de traitement. ces architectures vont passer pour des créations d'amateurs ! Nous allons parler des cartes graphiques modernes. Vieux GPUs Cela fait un moment que les cartes graphiques de nos ordinateurs sont devenues programmables. Mais comparé à ce qu'on va voir. Sur les premières cartes graphiques programmables. comprendre le fonctionnement de ces GPU devient un impératif pour faire du calcul à haute performances. ces processeurs sont tous identiques et peuvent servir à faire aussi bien des calculs graphiques que des calculs www. abréviées GPU.siteduzero. nos GPU peuvent effectuer des taches qui auraient étè dévolues au processeur il y a quelques années. et l'idée d'utiliser ceux-ci pour effectuer des calculs génériques s'est peu à peu répandue. Des couches logicielles comme CUDA ou OpenMP permettent ainsi de paralléliser des programmes et en exécuter certaines portions sur ces GPU.com .

sont appliqués entièrement à un tableau de donnée que l'on appelle un Stream. Sur ces processeurs. Dans nos cartes graphiques actuelles. on va commencer par la façon dont il gère la mémoire. ce Stream est découpé en morceaux qui seront chacun traités sur un Stream Processor. nommés Kernels. Un GPU actuel est souvent composé de plusieurs de ces Streams Processors. des processeurs spécialement conçus pour exécuter des suites d'instructions sur un grand nombre de données. Et pour cela. V ous remarquerez que le terme Thread est ici utilisé dans un sens différent de celui utilisé précédemment. Chacun de ces morceaux est appelé un Thread . les instructions du processeur travaillent sur des données de taille fixe : pour traiter un tableau complet. placés ensembles sur une même puce.siteduzero.Partie 5 : Le parallèlisme de données 91/97 sur des données quelconques. Rien de tout cela sur nos cartes 3D : on envoie à notre carte 3D des informations sur le tableau à manipuler et celle-ci se débrouille toute seule pour le découper en morceau et les répartir sur les processeurs disponibles. et qui sont utilisables par tous les Streams Processors de la puce. qui servent à stocker des constantes ou des textures de façon temporaire. V oici à quoi ressemble l'architecture d'un Stream Processor : www. C'est assez différent de ce qu'on trouve sur les processeurs SIMD ou vectoriels. Ces processeurs sont ce qu'on appelle des Streams Processors. L'ensemble est souvent complété par quelques petites mémoires. on doit utiliser une boucle pour traiter celui-ci morceau par morceau. C'est de plus en plus le cas sur les cartes graphiques actuelles. avant de les répartir sur les différents Streams Processors de la puce. des programmes. utilisés dans les taches de rendu 3D. ces processeurs ressemblent fortement aux processeur vectoriels ou aux processeurs utilisant des instructions SIMD. Faites attention ! Quoiqu'il en soit. avec quelques autres circuits annexes. On remarque que le découpage du Stream en Threads se fait à l’exécution. Ces Streams Processors sont alors pilotés par un gros micro-contrôleur qui se charge de découper le Stream à traiter en Threads. nous allons voir grosso modo ce que l'on trouve dans un Stream Processor. On retrouve parfois plusieurs mémoires caches partagées entre les différents Streams Processors. Hiérarchie mémoire Pour commencer. Sur ces derniers. qui est assez spéciale.com .

les mémoires caches peuvent se rendre utiles. on remarque que l'on en a plusieurs. On voit bien que si on utilise notre donnée une seule fois. nos Streams ne sont rien d'autres que des tableaux. on accède une première fois à notre donnée depuis la mémoire (ce qui est lent) et on la copie dans le cache. pour la réutiliser plus rapidement (un cache est plus rapide que la RAM). C'est pour cela que les premiers processeurs vectoriels et les Streams processors ont peu ou pas de cache. Pour éviter cela. Dans nos Streams Processors. comme pour les processeurs vectoriels. C'est dans ces Local Register File que nos unités de calcul vont aller chercher les données qu'elles doivent manipuler. Je tiens toutefois à nuancer ce que j'ai dit plus haut : si notre Stream Processor ne contient pas de mémoire cache pour les données. utiliser des mémoires caches est contre-productif. autant éviter de les charger de la mémoire à chaque fois. si l'on doit exécuter ces instructions plusieurs fois de suite sur des données différentes. conçus pour manipuler des tableaux de données plus ou moins complexes. il est rare qu'on doive la réutiliser plus tard. Register Files Sur un processeur. n'avaient strictement aucun cache. On trouve ainsi des caches L1.siteduzero. Déjà. www. Autant copier directement nos données dans les registres. On trouve d'abord quelques Local Register File . Avec un cache. Et encore. Il faut dire que les Streams Processors sont. On en trouve dans des tas de processeurs. De plus. Ceux-ci sont directement connectés aux unités de calcul. etc. le Global Register File . il n'y a pas de mémoires caches. Les premiers Streams Processors. Après tout.Partie 5 : Le parallèlisme de données 92/97 A première vue.com . Et pourtant on peut déjà faire quelques remarques assez intéressantes. L2. comme les processeurs vectoriels. ces Local Register Files sont reliés à un Register File plus gros. comme l'Imagine. lui-même relié à la mémoire. Il s'agit bel et bien d'une petite mémoire cache. les Streams Processors contiennent peu de mémoires caches. d'autres non. Ces caches sont utiles lorsque l'on utilise la carte graphique via des couches logicielles de GPGPU comme CUDA ou OpenMP : on exécute alors du code assez généraliste. et pas seulement dans des Streams Processors. les suites d'instructions à exécuter sont stockées dans une petite mémoire une bonne fois pour toute. les cartes graphiques récentes utilisent de plus en plus de mémoires caches disséminées un peu partout sur leur puce. les registres sont tous regroupés dans de grosses mémoires RAM qu'on appelle des Register Files. qui possède une localité pas trop mauvaise. les schémas du dessus ne ressemblent à rien de connu. Plus bas. ce n'est pas le cas pour les instructions. Pas de cache de données En théorie. notre cache ne sert à rien. c'est quand ils en ont. certains étant partagés. Ensuite. l'ensemble ressemble fortement à ce qu'on trouve sur les processeurs vectoriels. L3. ces tableaux ont une faible localité temporelle : quand on accède à une donnée dans un tableau. Il faut dire que celles-ci ont étés conçues pour stocker des données afin de pouvoir les réutiliser. Après tout. à part que les registres vectoriels sont scindés en plusieurs exemplaires. Dans ces conditions. Généralement. Dans ces conditions.

Partie 5 : Le parallèlisme de données

93/97

C'est très différent de ce qu'on trouve sur les processeurs plus habituels. D'habitude, on ne dispose pas de plusieurs couches de Register Files. A la place, on trouve une couche de registres, une couche de cache, une couche de mémoire. Sur les processeurs vectoriels, on trouve a la place une couche de registres vectoriels, avec une mémoire RAM. Alors pourquoi trouve-on plusieurs couches de registres ? Le fait est que nos Streams Processors et nos GPU disposent d'une grande quantité d'unités de calcul. Et cela peut facilement aller à plus d'une centaine ou d'un millier d'ALU ! Si on devait relier toutes cas unités de calcul à un gros Register File, cela poserait pas mal de problème. Si on faisait ça, on se retrouverait avec un Register File énorme, lent, et qui chaufferait beaucoup trop. Pour garder un Register Files rapide et pratique, on est obligé de limiter le nombre d'unités de calcul connectées dessus, ainsi que le nombre de registres contenus dans le Register File. La solution est donc de casser notre gros Register File en plusieurs plus petits, reliés à un Register File plus gros, capable de communiquer avec la mémoire. Ainsi, nos unités de calcul vont aller lire ou écrire dans un Local Register File très rapide.

Notre Global Register File va en quelque sorte d'intermédiaire entre la mémoire RAM et le Local Register File. Un peu comme une mémoire cache. La différence entre ce Global Register File et un cache vient du fait que les caches sont souvent gérés par le matériel, tandis que ces Register Files sont gérés via des instructions machines. Le processeur dispose ainsi d'instructions pour transférer des données entre les Register Files ou entre ceux-ci et la mémoire. Leur gestion peut donc être déléguée au logiciel, qui saura les utiliser au mieux. Notre Global Register File va servir à stocker un ou plusieurs Threads destinés à être traités par notre Stream Processor. Il peut aussi servir à transférer des données entre les Local Register Files, où à stocker des données globales, utilisées par des Clusters d'ALU différents. Quand à nos Local Register File, ils vont servir à stocker des morceaux de Threads en cours de traitement : tous les résultats temporaires vont aller dans ce Local Register File, afin d'être lus ou écrits le plus rapidement possible.

Du coté de la RAM
Les transferts de données entre la mémoire et le Global Register File ressemble fortement à ceux qu'on trouve sur les processeurs vectoriels. Généralement, un Stream Processor posséde quelques instructions capables de transférer des données entre ce Global Register File et la mémoire RAM. Et on trouve des instructions capables de travailler sur un grand nombre de données simultanées, des accès mémoires en Stride, en Scatter-Gather, etc. La mémoire RAM d'un Stream Processor est souvent une mémoire spéciale, divisée en plusieurs sous-mémoires auxquelles on peut accéder indépendamment. Ces mémoires indépendantes sont ce qu'on appelle des bancs mémoires, et on les a déjà évoqués dans le chapitre sur les processeurs vectoriels. Cette mémoire RAM est une mémoire qui possède souvent un gros débit binaire. Je rappelle que le débit binaire d'une mémoire

www.siteduzero.com

Partie 5 : Le parallèlisme de données

94/97

est le nombre de bits qu'elle peut transférer en une seule seconde. Par contre, cela ne signifie pas que cette mémoire est rapide : le temps mit pour lire ou écrire une donnée est assez important. Il ne faut pas faire la confusion entre le débit binaire et le temps d'accès. Pour faire une analogie avec les réseaux, le débit binaire peut être vu comme le débit de la mémoire, alors que le temps d'accès serait similaire au ping . Il est parfaitement possible d'avoir un ping élevé avec une connexion qui télécharge très vite, et inversement. Pour la mémoire, c'est similaire.

Une histoire de latence
Comme je l'ai dit plus haut, la mémoire RAM est très lente. Et quand on n'a pas de mémoires caches pour diminuer la latence des accès mémoire, on doit trouver une autre solution. Au lieu de chercher à diminuer la latence, les Streams Processors vont plutôt chercher à cacher cette dernière.

Long Pipeline
La première solution pour cela consiste à permettre à notre Stream Processor de continuer à exécuter des instructions pendant que l'on accède à la mémoire. Avec ce principe, on peut remplir les vides dus à l'accès mémoire par des instructions indépendantes de la donnée à lire ou à écrire. Cela fonctionne à condition que l'on aie suffisamment d'instructions à exécuter et que les lectures soient rares. Autant dire que c'est un cas assez rare. Et surtout, cela dépend fortement de la qualité du programme qui s’exécute, et de la qualité du compilateur qui a servi à compiler ce dernier.

SIMT
Depuis les Radeon X1800, on préfère utiliser une autre technique : le Single Instruction Multiple Threads , aussi appelé SIMT . L'idée est simplement de permettre à notre processeur de gérer un grand nombre de Threads et de changer de Thread si l'un d'entre eux doit accéder à la mémoire. Mais attention, comme je l'ai dit plus haut, sur un GPU, un Thread n'est rien d'autre qu'un morceau de donnée similaire aux paquets SIMD vus dans les chapitres précédents. Avec le SIMT , si jamais on doit accéder à la mémoire, on change de Thread . Au lieu de perdre son temps à attendre, on va ainsi remplir les vides en commençant à traiter d'autres données.

Jeu d'instruction
On peut se demander de quoi sont capables ces fameux Streams Processors, quels calculs ils sont capables d'effectuer. De ce point de vue, si on regarde les instructions qu'ils sont capables de faire, cela a beaucoup évolué au fil du temps. Les premiers processeurs traitant des Shaders avaient un jeu d'instruction assez restreint. On ne pouvait qu'effectuer des calculs arithmétiques les uns à la suite des autres, sans possibilités d'effectuer des choix (conditions if, else, etc), et sans pouvoir répéter des instructions (boucles). On trouvait de quoi faire des additions, des soustractions, des multiplications, mais rien de vraiment folichon. Au fil du temps, les GPU sont devenus de plus en plus programmables, et des instructions de tests et de branchements

www.siteduzero.com

Partie 5 : Le parallèlisme de données

95/97

ont fait leur apparition. Les GPU sont devenus capables d'effectuer des boucles, des tests voire même des appels de fonctions. En même temps, les calculs qu'ils peuvent effectuer se sont enrichis. Généralement, un Stream Processor peut effectuer des calculs sur des nombres entiers ou sur des nombres à virgule qu'on appelle des nombres à virgule flottante. On trouve les classiques additions, multiplications et opérations logiques (OU, ET, XOR, NON, etc).On trouve aussi des instructions qui permettent de calculer le maximum de deux nombres, leur moyenne, ou leur minimum). On a aussi des instructions d'arrondis pour les nombres à virgule flottante. Parfois, le Stream Processor est capable d'effectuer des opérations plus complexes comme des exponentielles, des racines carrées, le calcul de cosinus, etc. On peut aussi citer le fait que les GPU récents supportent des instructions atomiques, utilisée pour partager la mémoire. Cela peut servir dans certains algorithmes. , , des sinus, des

Plusieurs ALU
Pour effectuer ces calculs, chaque Stream Processor contient des unités de calcul. Ces unités de calcul d'un Stream Processor ne sont pas forcément identiques. On peut parfaitement avoir quelques ALU capables d'effectuer des additions et multiplications (plus quelques opérations très simples du genre), à coté d'une grosse ALU capable d'effectuer des opérations couteuses et lentes (exponentielle, etc). On trouve aussi souvent une unité chargée de la gestion des accès mémoires, et notamment des transferts de données entre le Local Register File et le Global Register File. Ces unités de calcul ne sont pas commandées n'importe comment : il faut bien répartir nos calculs sur nos unités de calcul pour les faire fonctionner en parallèle. Il existe deux grandes façons de les piloter sur un Stream processor ou un GPU.

VLIW
Première solution : faire de nos Streams Processors des processeurs VLIW. Sur ces processeurs VLIW, nos instructions sont regroupées dans ce qu'on appelle des Bundles, des sortes de super-instructions. Ces bundles sont découpés en slots, en morceaux de taille bien précise, dans lesquels il va venir placer les instructions élémentaires à faire éxecuter. Instruction Slot 1 Addition VLIW Slot 2 Multiplication à 3 slots Slot 3 Décalage à gauche

0111 1111 0000 0110 1111 0101 0110 1001 0101

Chaque slot sera attribué à une unité de calcul bien précise. Par exemple, le premier slot sera attribué à la première ALU, la second à une autre ALU, le troisième à la FPU, etc. Ainsi, l'unité de calcul exécutant l'instruction sera précisée via la place de l'instruction élémentaire, le slot dans lequel elle se trouve. Qui plus est, vu que chaque slot sera attribué à une unité de calcul différente, le compilateur peut se débrouiller pour que chaque instruction dans un bundle soit indépendante de toutes les autres instructions dans ce bundle. Lorsqu'on exécute un bundle, il sera décomposée par le séquenceur en petites instructions élémentaires qui seront chacune attribuée à l'unité de calcul précisée par le slot qu'elles occupent. Pour simplifier la tache du décodage, on fait en sorte que chaque slot aie une taille fixe.

SIMD
Autre solution : faire en sorte que nos unités de calcul soient utilisées par des instructions SIMD. Ainsi, toutes nos unités de calculs sont identiques et exécutent simultanément la même instruction à chaque cycle d'horloge. On se retrouve ainsi avec la même situation que pour les processeurs vectoriels avec plusieurs lanes ou comme avec les processeurs utilisant des instructions SIMD utilisant une ALU par donnée d'un paquet SIMD. Cette solution est assez récente et date des GPU Geforce 8800. Les cartes graphiques récentes utilisent toutes des instructions SIMD et non des instructions VLIW. Il faut dire que le VLIW est certes très adapté pour les calculs de rendu 3D, mais est nettement moins adapté aux calculs généralistes. Et avec l'apparition de CUDA et des autres openMP et librairies de traitement GPGPU, le VLIW commence à perdre de l’intérêt.

Prédication
Ces instructions SIMD sont très utiles quand on doit effectuer un traitement identique sur un ensemble de données identiques. Mais dès que le traitement à effectuer sur nos données varie suivant le résultat d'un test ou d'un branchement, les choses se gâtent. Mine de rien, avec une instruction vectorielle, on est obligé de calculer et de modifier tous les éléments d'un paquet : il est impossible de zapper certains éléments d'un paquet dans certaines conditions. Par exemple, imaginons que je veuille

www.siteduzero.com

Ce Vector Mask Register va stocker des bits pour chaque flottant présent dans le vecteur à traiter. nos Streams Processors et nos GPU utilisent une technique vue sur les processeurs vectoriels : le Vector Mask Register. notre instruction doit s’exécuter sur la donnée associée à ce bit. 96/97 Pour résoudre ce problèmes. notre instruction ne doit pas la modifier. www. Si ce bit est à 1. et décider quoi mettre dans ce registre de masquage en fonction de chaque résultat.siteduzero. On peut ainsi traiter seulement une partie des données via une instruction VLIW ou SIMD. Du moins. suivant les circonstances. Certaines instructions peuvent ainsi comparer le contenu de deux registres. pas sans aide. Celui-ci permet de stocker des informations qui permettront de sélectionner certaines données et pas d'autres pour faire notre calcul.com . Nos Streams Processors ont souvent une grande quantité d'instructions pour mettre à jour ce Vector Mask Register.Partie 5 : Le parallèlisme de données seulement additionner les éléments d'un paquet ensemble s'ils sont positifs : je ne peux pas le faire avec une instruction vectorielle "normale". données par données. Sinon.

Sign up to vote on this title
UsefulNot useful