Renommage de registres

Renommage de registres

En architecture des ordinateurs, on nomme 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.

Sommaire

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, 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 de mem[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

  1. William Stallings, Organisation et architecture de l'ordinateur, 6e édition, Pearson Education, 2003, p. 567.
  2. ibid., p. 556.