Si les registres sont si rapides, pourquoi n'en avons-nous pas plus?

88

En 32 bits, nous avions 8 registres «à usage général». Avec 64 bits, le montant double, mais cela semble indépendant du changement 64 bits lui-même.
Maintenant, si les registres sont si rapides (pas d'accès à la mémoire), pourquoi n'y en a-t-il pas plus naturellement? Les constructeurs de CPU ne devraient-ils pas travailler autant de registres que possible dans le CPU? Quelle est la restriction logique pour laquelle nous n'avons que le montant dont nous disposons?

Xeo
la source
Les processeurs et les GPU masquent la latence principalement par les caches et le multithreading massif respectivement. Ainsi, les processeurs ont (ou ont besoin) de quelques registres, alors que les GPU ont des dizaines de milliers de registres. Voir mon document d'enquête sur le fichier de registre GPU qui traite de tous ces compromis et facteurs.
user984260

Réponses:

119

Il y a de nombreuses raisons pour lesquelles vous n'avez pas seulement un grand nombre de registres:

  • Ils sont étroitement liés à la plupart des étapes du pipeline. Pour commencer, vous devez suivre leur durée de vie et renvoyer les résultats aux étapes précédentes. La complexité devient intraitable très rapidement, et le nombre de fils (littéralement) impliqués croît au même rythme. C'est cher sur la surface, ce qui signifie finalement que c'est cher en puissance, en prix et en performances après un certain point.
  • Il prend de l'espace d'encodage d'instructions. 16 registres occupent 4 bits pour la source et la destination, et 4 autres si vous avez des instructions à 3 opérandes (par exemple ARM). C'est énormément d'espace d'encodage de jeu d'instructions occupé juste pour spécifier le registre. Cela a finalement un impact sur le décodage, la taille du code et encore une fois la complexité.
  • Il y a de meilleures façons d'obtenir le même résultat ...

De nos jours, nous avons vraiment beaucoup de registres - ils ne sont tout simplement pas explicitement programmés. Nous avons "renommer le registre". Bien que vous n'accédiez qu'à un petit ensemble (8-32 registres), ils sont en fait soutenus par un ensemble beaucoup plus grand (par exemple 64-256). La CPU suit ensuite la visibilité de chaque registre et les attribue à l'ensemble renommé. Par exemple, vous pouvez charger, modifier, puis stocker dans un registre plusieurs fois de suite, et faire exécuter chacune de ces opérations indépendamment en fonction des échecs de cache, etc. Dans ARM:

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Les cœurs Cortex A9 renomment les registres, donc le premier chargement vers "r0" va en fait vers un registre virtuel renommé - appelons-le "v0". Le chargement, l'incrémentation et le stockage se produisent sur "v0". Pendant ce temps, nous effectuons à nouveau un chargement / modification / stockage sur r0, mais cela sera renommé en "v1" car il s'agit d'une séquence entièrement indépendante utilisant r0. Disons que la charge du pointeur dans "r4" est bloquée en raison d'un manque de cache. Ce n'est pas grave - nous n'avons pas besoin d'attendre que "r0" soit prêt. Parce qu'il est renommé, nous pouvons exécuter la séquence suivante avec "v1" (également mappé sur r0) - et peut-être que c'est un succès de cache et que nous venons d'avoir une énorme victoire en termes de performances.

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

Je pense que x86 est jusqu'à un nombre gigantesque de registres renommés ces jours-ci (environ 256). Cela signifierait avoir 8 bits fois 2 pour chaque instruction juste pour dire quelle est la source et la destination. Cela augmenterait massivement le nombre de fils nécessaires à travers le noyau et sa taille. Il y a donc un sweet spot autour de 16-32 registres que la plupart des concepteurs ont choisi, et pour les conceptions de CPU en désordre, le changement de nom de registre est le moyen de l'atténuer.

Edit : L'importance de l'exécution dans le désordre et du renommage du registre à ce sujet. Une fois que vous avez OOO, le nombre de registres n'a pas tant d'importance, car ce ne sont que des "balises temporaires" et sont renommées en un ensemble de registres virtuels beaucoup plus grand. Vous ne voulez pas que le nombre soit trop petit, car il devient difficile d'écrire de petites séquences de code. C'est un problème pour x86-32, car les 8 registres limités signifient que beaucoup de temporaires finissent par passer par la pile, et le noyau a besoin d'une logique supplémentaire pour transférer les lectures / écritures vers la mémoire. Si vous n'avez pas OOO, vous parlez généralement d'un petit noyau, auquel cas un grand jeu de registres est un faible avantage en termes de coût / performance.

Il existe donc un sweet spot naturel pour la taille de la banque de registres, qui atteint au maximum environ 32 registres architecturés pour la plupart des classes de CPU. x86-32 a 8 registres et il est définitivement trop petit. ARM est allé avec 16 registres et c'est un bon compromis. 32 registres, c'est un peu trop, voire pas du tout - vous finissez par ne pas avoir besoin des 10 derniers environ.

Rien de tout cela ne touche aux registres supplémentaires que vous obtenez pour SSE et d'autres coprocesseurs vectoriels à virgule flottante. Ceux-ci ont du sens en tant qu'ensemble supplémentaire car ils fonctionnent indépendamment du cœur entier et n'augmentent pas la complexité du processeur de manière exponentielle.

John Ripley
la source
12
Excellente réponse - j'aimerais ajouter une autre raison au mélange - plus on a de registres, plus il faut de temps pour les jeter / les retirer de la pile lors du changement de contexte. Certainement pas le problème majeur, mais une considération.
Will A
7
@WillUn bon point. Cependant, les architectures avec beaucoup de registres ont des moyens d'atténuer ce coût. L'ABI aura généralement une sauvegarde appelée de la plupart des registres, vous n'avez donc qu'à sauvegarder un ensemble de base. Le changement de contexte est généralement assez cher pour que la sauvegarde / restauration supplémentaire ne coûte pas cher par rapport à toutes les autres formalités administratives. SPARC fonctionne en fait autour de cela en faisant de la banque de registres une "fenêtre" sur une zone de mémoire, donc il évolue quelque peu avec cela (un peu agité à la main).
John Ripley
4
Considérez mon esprit époustouflé par une réponse si approfondie à laquelle je ne m'attendais certainement pas. Aussi, merci pour cette explication sur la raison pour laquelle nous n'avons pas vraiment besoin de tant de registres nommés, c'est très intéressant! J'ai beaucoup aimé lire votre réponse, car je suis totalement intéressé par ce qui se passe "sous le capot". :) Je vais attendre encore un peu avant d'accepter une réponse, car on ne sait jamais, mais mon +1 est sûr.
Xeo
1
quelle que soit la responsabilité de la sauvegarde des registres, le temps que cela prend est une surcharge administrative. OK, le changement de contexte n'est peut-être pas le cas le plus fréquent, mais les interruptions le sont. Les routines codées à la main peuvent économiser sur les registres, mais si les pilotes sont écrits en C, il y a des chances que la fonction déclarée par interruption sauvegarde chaque registre unique, appelle l'isr puis restaure tous les registres sauvegardés. IA-32 avait un avantage d'interruption avec ses 15-20 regs par rapport à 32+ regs des architectures RISC.
Olof Forshell
1
Excellente réponse, mais je ne suis pas d'accord avec la comparaison directe des registres «renommés» avec des registres adressables «réels». Sur x86-32, même avec 256 registres internes, vous ne pouvez pas utiliser plus de 8 valeurs temporaires stockées dans des registres en un seul point d'exécution. Fondamentalement, le changement de nom de registre n'est qu'un curieux sous-produit d'OOE, rien de plus.
noop
12

Nous faisons avoir plus d'eux

Étant donné que presque chaque instruction doit sélectionner 1, 2 ou 3 registres architecturaux visibles, augmenter le nombre d'entre eux augmenterait la taille du code de plusieurs bits sur chaque instruction et réduirait ainsi la densité du code. Cela augmente également la quantité de contexte qui doit être enregistrée en tant qu'état de thread et partiellement enregistrée dans l' enregistrement d'activation d' une fonction . Ces opérations sont fréquentes. Les verrouillages de pipeline doivent vérifier un tableau de bord pour chaque registre, ce qui présente une complexité quadratique dans le temps et dans l'espace. Et peut-être que la principale raison est simplement la compatibilité avec le jeu d'instructions déjà défini.

Mais il s'avère que grâce au changement de nom des registres , nous avons vraiment beaucoup de registres disponibles, et nous n'avons même pas besoin de les sauvegarder. Le CPU a en fait de nombreux jeux de registres, et il bascule automatiquement entre eux lorsque votre code exeutes. Il le fait uniquement pour vous obtenir plus de registres.

Exemple:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

Dans une architecture qui n'a que r0-r7, le code suivant peut être réécrit automatiquement par le CPU comme quelque chose comme:

load  r1, a
store r1, x
load  r10, b
store r10, y

Dans ce cas, r10 est un registre caché qui remplace temporairement r1. Le CPU peut dire que la valeur de r1 n'est plus jamais utilisée après le premier stockage. Cela permet de retarder le premier chargement (même un hit de cache sur puce prend généralement plusieurs cycles) sans nécessiter le retard du deuxième chargement ou du deuxième stockage.

DigitalRoss
la source
2

Ils ajoutent des registres tout le temps, mais ils sont souvent liés à des instructions spéciales (par exemple SIMD, SSE2, etc.) ou nécessitent une compilation sur une architecture de processeur spécifique, ce qui réduit la portabilité. Les instructions existantes fonctionnent souvent sur des registres spécifiques et ne pourraient pas profiter d'autres registres s'ils étaient disponibles. Ensemble d'instructions hérité et tout.

Seth Robertson
la source
1

Pour ajouter quelques informations intéressantes ici, vous remarquerez qu'avoir 8 registres de même taille permet aux opcodes de maintenir la cohérence avec la notation hexadécimale. Par exemple, l'instruction push axest l'opcode 0x50 sur x86 et va jusqu'à 0x57 pour le dernier registre di. Ensuite, l'instruction pop axcommence à 0x58 et monte à 0x5F pop dipour terminer la première base-16. La cohérence hexadécimale est maintenue avec 8 registres par taille.


la source
2
Sur x86 / 64, les préfixes d'instructions REX étendent les indices de registre avec plus de bits.
Alexey Frunze