En architecture des ordinateurs, on appelle renommage de registres le fait qu'une microarchitecture alloue dynamiquement les registres architecturaux à un ensemble plus vaste de registres physiques au cours de l'exécution d'un programme.
Position du problème
Une architecture externe de processeur définit un ensemble de registres, dits architecturaux[1], que peuvent manipuler les programmes en langage machine.
Dans une microarchitecture superscalaire, le processeur essaie d'exécuter en parallèle plusieurs instructions. Il analyse donc localement le programme afin d'y déceler les dépendances entre instructions, et réordonne ces dernières en conséquence, de façon à tirer parti du parallélisme tout en n'introduisant pas d'erreur. Or les dépendances entre instructions machine limitent les performances de l'exécution dans le désordre, car il arrive régulièrement que plusieurs instructions soient en compétition pour l'utilisation du même registre en raison du parallélisme qui a été introduit[2]. Il faut alors bloquer l'une des instructions en attendant que la ressource se libère.
Cependant, dans de nombreux cas, ces dépendances n'apparaissent qu'au niveau des registres, mais ne reflètent pas de réelles dépendances dans les flux de données traités par le programme. Ce problème est d'ailleurs d'autant plus prégnant que le compilateur a effectué des optimisations basées sur l'utilisation des registres.
Solution : renommage de registres
Une solution consiste donc à dupliquer les ressources : les registres architecturaux ne correspondent plus à des registres physiques dans la microarchitecture, mais sont alloués dynamiquement à un ensemble plus grand de registres physiques, ce qui permet d'éliminer une partie des dépendances introduites artificiellement par le nombre restreint de registres. Par exemple, l'architecture IA-32 définit 16 registres architecturaux. Le Pentium dispose de 128 registres physiques et effectue du renommage de registres.
Exemple
Considérons par exemple le code suivant :
1. R1 ← mem[1] 2. R1 ← R1 + 1 3. mem[1] ← R1 4. R1 ← mem[10] 5. R1 ← R1 + 4 6. mem[11] ← R1
D'un point de vue fonctionnel, il est clair que nous avons deux blocs indépendants de 3 instructions :
- les instructions 1-3 augmentent
mem[1]
d'une unité ; - les instructions 4-6 rangent dans
mem[11]
la valeur demem[10] + 4
.
Cependant, ces deux blocs utilisent tous les deux le registre R1
pour les calculs intermédiaires. Le processeur ne peut pas entamer 4 tant que 3 n'est pas terminée, ce qui limite drastiquement l'avantage d'une architecture superscalaire.
Supposons maintenant que le processeur effectue des renommages de registres. À chaque fois qu'une valeur est rangée dans un registre architectural, il la range dans un registre physique différent. Les différents registres physiques alloués au même registre architectural sont repérés par un suffixe d'une lettre :
1. R1a ← mem[1] 2. R1b ← R1a + 1 3. mem[1] ← R1b 4. R1c ← mem[10] 5. R1d ← R1c + 4 6. mem[11] ← R1d
De cette façon, les dépendances qui existaient au niveau registres entre 1-3 et 4-6 ont disparu : ces deux blocs peuvent être exécutées en parallèle, ce qui tire pleinement parti de l'architecture superscalaire du processeur.
1. R1a ← mem[1] 4. R1c ← mem[10] 2. R1b ← R1a + 1 5. R1d ← R1c + 4 3. mem[1] ← R1b 6. mem[11] ← R1d
Notes et références
- Stallings 2003, p. 567.
- Stallings 2003, p. 556.
Voir aussi
Articles connexes
Bibliographie
- [Stallings 2003] William Stallings, Organisation et architecture de l'ordinateur, Pearson Education, , 6e éd.