« Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs » : différence entre les versions

m
[[File:Partage des caches sur un processeur multicoeurs.png|centre|vignette|upright=2.0|Partage des caches sur un processeur multicœur.]]
 
==CohérenceLa cohérence des caches==
 
Les caches dédiés ont un problème, que je vais introduire par un exemple. Prenons deux processeurs qui ont chacun une copie d'une donnée dans leur cache. Si un processeur modifie sa copie de la donnée, l'autre ne sera pas mise à jour. L'autre processeur manipule donc une donnée périmée : il n'y a pas cohérence des caches.
 
[[File:Cohérence des caches.png|centre|vignette|upright=2|Cohérence des caches]]
 
Les caches partagés ne posent aucun problème de cohérence. Avec eux, une donnée n'est pas dupliquée en plusieurs exemplaires, mais n'est présente qu'une seule fois dans tout le cache. Ainsi, si on écrit celle-ci, on peut être sûr que les autres processeurs liront la donnée mise à jour et non une ancienne version. Vous remarquerez que sur le schéma, la mémoire RAM contient encore une autre version de la donnée (du moins, si on utilise un cache Write Back). Mais cela ne pose pas de problème : les processeurs ne pourront pas accéder à la donnée en RAM, et iront toujours chercher la donnée dans le cache. Le cache est conçu pour. Au final, notre processeur aura donc toujours accès à la dernière valeur écrite par les autres processeurs.
 
[[File:Shared cache coherency.png|centre|vignette|upright=2|Cohérence et caches partagés]]
 
On voit donc que la cohérence des caches n'est pas automatiquement maintenue avec des caches dédiés. Pour maintenir cette '''cohérence des caches''', on a inventé de quoi détecter les données périmées et les mettre à jour : des '''protocoles de cohérence des caches'''. Il existe beaucoup de protocoles de cohérence des caches, comme vous pouvez en juger par cette liste non-exhaustive :
 
* MSI protocol ;
* MESI, aussi appelé Illinois protocol ;
Avec l’'''espionnage du bus''', les caches interceptent les écritures sur le bus (qu'elles proviennent ou non des autres processeurs), afin de mettre à jour la ligne de cache ou de la périmer. Avec ces protocoles, chaque ligne de cache contient des bits qui indique si la donnée contenue est à jour ou périmée. Quand on veut lire une donnée, les circuits chargés de lire dans le cache vont vérifier ces bits. Si ces bits indiquent que la ligne de cache est périmée, le processeur va lire les données depuis la RAM ou les autres caches et mettre à jour la ligne de cache. La mise à jour de ces bits de validité a lieu à chaque écriture (y compris les caches des autres cœurs). Plus précisément, nos caches sont interconnectés entre eux, afin de maintenir la cohérence. Si un cache contient une donnée partagée, ce cache devra prévenir tous les autres caches qu'une donnée a été mise à jour. La mise à jour des données périmées peut être automatique ou basée sur une invalidation. Avec la '''mise à jour sur écriture''', les caches sont mis à jour automatiquement le plus tôt possible, avant même toute tentative de lecture. Avec l''''invalidation sur écriture''', toute écriture invalide les versions de la donnée dans les autres caches. Ces versions seront mises à jour quand le processeur les lira : il détecta que la ligne de cache a été invalidée et la mettra à jour si besoin.
 
===ProtocoleLes protocoles sans nouveaux états===
 
Pour commencer, nous allons voir le protocole de cohérence des caches le plus simple : le protocole SI. Ce protocole ne fonctionne qu'avec un certain type de mémoires caches : les caches Write-trough. 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. Le contenu de la mémoire est donc toujours à jour, mais ce n'est pas le cas pour les caches des autres processeurs.
 
[[File:Cohérence des caches write-through.png|centre|800pxvignette|upright=2.5|Cohérence des caches write-through.]]
 
Dans ces conditions, seuls deux états suffisent pour décrire l'état d'une ligne de cache : Shared, qui indique que la ligne de cache est cohérente et Invalid, qui indique que la donnée est périmée. On obtient le protocole de cohérence des caches le plus simple qui soit : le protocole SI. Voici décrit en image le fonctionnement de ce protocole :
 
[[File:Diagramme d'état du protocole SI.png|centre|vignette|upright=2|Diagramme d'état du protocole SI.]]
 
===ProtocolesLes protocoles à état Shared===
 
La cohérence des caches est très simple quand on a des caches write-trough. Malheureusement, ces caches sont à l'origine de beaucoup d'écritures en mémoire qui saturent le bus. Aussi, d'autres caches ont étés inventés, qui n'ont pas ce problème : les caches Write-back, où le contenu de la mémoire n'est pas cohérent avec le contenu du cache. Si on écrit dans la mémoire cache, le contenu de la mémoire RAM n'est pas mis à jour. 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) : cela évite de nombreuses écritures mémoires inutiles.
|}
 
===ProtocolesLes protocoles à état Exclusif===
 
Le protocole MSI n'est pas parfait : si un seul cache possède une donnée, on aura prévenu les autres caches pour rien en cas d'écriture. Ces communications sur le bus ne sont pas gratuites et en faire trop peu ralentir fortement notre ordinateur. Pour régler ce problème, on a scindé l'état Shared en deux états : Exclusive si les autres processeurs ne possèdent pas de copie de la donnée, Shared sinon. Avec cette distinction, 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, alors il ne sert à rien de prévenir inutilement. Le '''protocole MESI''' ainsi créé est identique au protocole MSI, avec quelques ajouts. Par exemple, si une donnée est chargée depuis la mémoire pour la première fois dans un cache, elle passe soit en Exclusive (les autres caches ne contenaient pas la donnée), soit en Shared (les autres caches en possèdent une copie). Une donnée marquée Exclusive peut devenir Shared si la donnée est chargée dans le cache d'un autre processeur.
Comment le processeur fait-il pour savoir si les autres caches ont une copie de la donnée ? Pour cela, il faut ajouter un fil Shared sur le bus, qui sert à dire si un autre cache a une copie de la donnée. Lors de chaque lecture, l'adresse à lire sera envoyée à tous les caches, qui vérifieront s'ils possèdent une copie de la donnée. Une fois le résultat connu, chaque cache fournit un bit qui indique s'il a une copie de la donnée. Le bit Shared est obtenu en effectuant un OU logique entre toutes les versions du bit envoyé par les caches.
 
===ProtocoleLe protocole à état Owned===
 
Les protocoles MESI et MSI ne permettent pas de transférer des données entre les caches sans passer par la mémoire. Si le processeur demande la copie valide d'une donnée, tous les caches ayant la bonne version de la donnée vont répondre en même temps : la donnée est envoyée en plusieurs exemplaires ! Pour éviter ce problème, on doit rajouter un état supplémentaire : l'état Owned. Si un processeur écrit dans son cache, il mettra sa donnée en Owned. Les autres caches passeront leur donnée en version Modified, voire Shared une fois la mémoire mise à jour : ainsi, un seul processeur pourra avoir une donnée dans l'état Owned. Seul le processeur qui possède la donnée en Owned va répondre aux demandes de mise à jour (ceux possédant la donnée Shared ne répondant pas à la demande formulée par le processeur demandeur).
Divers protocoles de cohérences des caches utilisent cet état Owned. Le premier d’entre eux est le '''protocole MOESI''', un protocole MESI auquel on a jouté l'état O.
 
[[File:MOESI State Transaction Diagram.svg|centre|vignette|upright=2|MOESI State Transaction Diagram]]
 
Cependant, celui-ci n'est pas le seul. On peut notamment citer le '''protocole MOSI''', une variante du MESI où l'état exclusif est remplacé par l'état O.
 
Lors d'une lecture, le cache va vérifier si la lecture envoyée sur le bus correspond à une de ses données. Mais cette vérification va prendre du temps, et le processeur va devoir attendre un certain temps. Si au bout d'un certain temps, aucun cache n'a répondu, le processeur postule qu'aucun cache n'a la donnée demandée et va lire la donnée en mémoire. Ce temps est parfois fixé une fois pour toute lors de la création des processeurs, mais il peut aussi être variable, qui est géré comme suit :
 
* pour savoir si un cache contient une copie de la donnée demandée, chaque cache devra répondre en fournissant un bit ;
* quand le cache a terminé la vérification, il envoie un 1 sur une sortie spécifique, et un 0 sinon ;
39 492

modifications