Pourquoi un processeur a-t-il 32 registres?

52

Je me suis toujours demandé pourquoi les processeurs s'arrêtaient à 32 registres. C'est de loin la pièce la plus rapide de la machine, pourquoi ne pas simplement faire de plus gros processeurs avec plus de registres? Cela ne signifierait-il pas moins d'aller à la RAM?

Matt Capone
la source
2
Je suppose qu'au-delà d'un certain point, toutes vos variables locales entrent dans les registres. Les données réelles sur lesquelles vous travaillez sont probablement trop volumineuses
Niklas B.
14
Rendements décroissants. Clairement, les registres sont "plus chers" (dans divers sens) que la RAM ou nous aurions juste 8 Go de registres.
David Richerby
5
L'une des raisons pour lesquelles c'est si rapide, c'est qu'il n'y en a pas beaucoup.
stackErr
5
Il y a une différence entre le nombre total de registres du processeur et le nombre de registres que vous pouvez utiliser simultanément.
Thorbjørn Ravn Andersen
Les processeurs et les GPU masquent la latence principalement par les caches et les multithreads, respectivement. Ainsi, les processeurs ont peu de registres, alors que les GPU ont des dizaines de milliers de registres. Voir mon article d'enquête sur le fichier du registre GPU qui traite de tous ces compromis et facteurs.
user984260

Réponses:

82

Premièrement, toutes les architectures de processeur ne se sont pas arrêtées sur 32 registres. Presque toutes les architectures RISC qui ont 32 registres exposés dans le jeu d'instructions ont en réalité 32 registres d'entiers et 32 ​​registres de plus en virgule flottante (donc 64). (Le virgule flottante "add" utilise des registres différents du nombre entier "add".) L’architecture SPARC a des fenêtres de registre.. Sur SPARC, vous ne pouvez accéder qu'à 32 registres d'entiers à la fois, mais les registres agissent comme une pile et vous pouvez pousser et faire apparaître de nouveaux registres 16 à la fois. L'architecture Itanium de HP / Intel comportait 128 registres d'entiers et 128 registres à virgule flottante exposés dans le jeu d'instructions. Les GPU modernes de NVidia, AMD, Intel, ARM et Imagination Technologies exposent tous un grand nombre de registres dans leurs fichiers. (Je sais que cela est vrai des architectures NVidia et Intel, je ne connais pas très bien les jeux d'instructions AMD, ARM et Imagination, mais je pense que les fichiers de registre sont volumineux également.)

Deuxièmement, la plupart des microprocesseurs modernes implémentent le changement de nom de registre de pour éliminer la sérialisation inutile causée par la nécessité de réutiliser les ressources. Ainsi, les fichiers de registre physique sous-jacents peuvent être plus volumineux (96, 128 ou 192 registres sur certaines machines). Ceci élimine une partie des fichiers. nécessité pour le compilateur de générer autant de noms de registre uniques, tout en fournissant un fichier de registre plus volumineux au planificateur.

Il peut être difficile d’augmenter encore le nombre de registres exposés dans le jeu d’instructions pour deux raisons. Tout d'abord, vous devez pouvoir spécifier les identificateurs de registre dans chaque instruction. 32 registres nécessitent un spécificateur de registre sur 5 bits. Par conséquent, les instructions à 3 adresses (communes sur les architectures RISC) utilisent 15 bits sur 32 bits pour spécifier les registres. Si vous augmentiez cela à 6 ou 7 bits, vous disposeriez de moins d'espace pour spécifier des codes opération et des constantes. Les GPU et Itanium ont des instructions beaucoup plus volumineuses. Les instructions plus volumineuses ont un coût: vous devez utiliser plus de mémoire d'instructions, votre comportement en cache d'instructions est donc moins idéal.

La deuxième raison est le temps d'accès. Plus vous créez une mémoire importante, plus il est lent pour accéder aux données de celle-ci. (Juste en termes de physique de base: les données sont stockées dans un espace à 2 dimensions, donc si vous stockez bits, la distance moyenne par rapport à un bit spécifique est O ( n.) Un fichier de registre est juste une petite mémoire multiport, et l’une des contraintes pour l’agrandir est qu’il soit nécessaire de commencer à synchroniser votre machine plus lentement pour pouvoir stocker le fichier de registre plus volumineux. Habituellement, en termes de performance totale, c'est une perte. O(n)

Logique errante
la source
1
J'aurais mentionné les 256 FPR et les 32 GPR non-fenêtre supplémentaires de SPARC64 VIIIfx, obtenus en ajoutant une instruction Set XAR qui fournit 13 bits chacun pour la ou les prochaines instructions. Il visait le calcul haute performance, de sorte que le nombre de registres est plus compréhensible. J'aurais aussi été tenté d'expliquer certains des compromis et des techniques associés à davantage de registres; mais vous avez fait preuve de sagesse pour éviter une réponse plus épuisante (et même alors non exhaustive).
Paul A. Clayton
2
Il serait peut-être utile d’ajouter un peu plus d’avantages moins nombreux à avoir plus de registres pour le code «à usage général», bien qu’il soit difficile de trouver des mesures significatives. Je pense que Mitch Alsup a mentionné sur comp.arch qu'étendre l'extension de x86 à 32 registres plutôt que 16 aurait généré une performance d'environ 3%, par rapport à (ISTR) 10-15% pour l'extension de registre choisie de 8 à 16. Même pour un ISA à chargement, passer à 64 offre probablement peu d'avantages (du moins pour le code GP actuel). (En passant, les GPU partagent souvent des registres sur plusieurs threads: par exemple, un thread sur 250 total sur 16 privés pour d'autres threads.)
Paul A. Clayton
Il est intéressant de voir que la gestion de l'environnement (d'où l'alpha-conversion), souvent associée aux langages de haut niveau, est en fait utilisée au niveau du registre.
Babou
@ PaulA.Clayton J'ai toujours pensé que l'IA-64 était l'architecture qui comptait le plus grand nombre de registres ISA
phuclv
@ LưuVĩnhPhúc SPARC64 VIIIfx était spécifique à HPC. Pour votre information, l’ Am29k (introduit vers 1987-8 ) comptait 64 GPR globaux et 128 fenêtrés, soit plus de GPR que d’Itanium (qui possède 8 registres de branche et un registre de comptage de boucles dont la fonction serait dans les GPR de certains autres ISA).
Paul A. Clayton
16

Encore deux raisons de limiter le nombre de registres:

  • Peu de gain à attendre: les processeurs tels que les modèles Intel / AMD x64 actuels ont 32 ko et plus de cache L1-D, et l'accès au cache L1 ne prend généralement qu'un cycle d'horloge (comparé à une centaine de cycles d'horloge pour une seule RAM accès). Il y a donc peu à gagner à avoir plus de données dans les registres que d'avoir des données dans le cache N1
  • Coûts de calcul supplémentaires: le fait d’avoir plus de registres crée une surcharge qui peut en réalité ralentir un ordinateur:
    • Dans les environnements multitâches, un commutateur de tâches doit généralement enregistrer le contenu de tous les registres du processus laissés en mémoire et charger ceux du processus à saisir. Plus vous avez de registres, plus cela prend de temps.
    • De même, dans les architectures sans fenêtre de registre, les appels de fonction en cascade utilisent le même jeu de registres. Ainsi, une fonction A appelant une fonction B utilise le même ensemble de registres que B lui-même. Par conséquent, B doit sauvegarder le contenu de tous les registres qu’il utilise (qui contient toujours les valeurs de A) et doit les réécrire avant de les renvoyer (dans certaines conventions d’appel, il incombe à A de sauvegarder le contenu de son registre avant d’appeler B, les frais généraux sont similaires). Plus vous avez de registres, plus cette sauvegarde prend longtemps, plus un appel de fonction devient coûteux.
Robert Buchholz
la source
Comment cela fonctionne-t-il pour le cache L1 afin que nous n'ayons pas le même problème que pour les registres?
Babou
4
Sur les processeurs hautes performances, la latence L1 Dcache est généralement comprise entre 3 et 4 cycles (y compris la génération d'adresses), par exemple, Haswell d'Intel dispose d'une latence de 4 cycles (l'absence de latence dans les registres de dépendance des données est également plus facile à masquer dans le pipeline). Dcache a également tendance à prendre en charge moins d’accès par cycle (par exemple, 2 lectures, 1 en écriture pour Haswell) qu’un fichier de registre (par exemple, 4 lectures, 6 en écriture pour Alpha 21264 qui a répliqué le fichier, 2 fichiers avec 4 lectures est plus rapide que 1 avec 8).
Paul A. Clayton
@ PaulA.Clayton: Si le cache L1 a une latence de 3-4 cycles, cela suggérerait qu'il pourrait être avantageux de disposer, par exemple, de quelques jeux de 64 mots de mémoire à cycle unique avec son propre espace d'adressage de 64 mots, et des instructions "chargement / stockage direct" dédiées, en particulier s'il existe un moyen de transmettre toutes les valeurs non nulles suivies d'un mot indiquant quels mots sont non nuls, puis un moyen de les restaurer (remettant à zéro tous les registres non ouverts) . De nombreuses méthodes utilisent entre 16 et 60 mots de variables locales. Il serait donc utile de réduire le temps d'accès de 3 à 4 cycles à ceux d'un cycle.
Supercat
@supercat Diverses idées de cache de pile (et de global / TLS [par exemple, Knapsack]) ont été présentées dans des documents scientifiques ainsi que dans des mécanismes tels que le tampon de signature ( PDF ). Utilisation réelle, pas tellement (semble-t-il). Cela devient bavard (devrait donc probablement se terminer ou aller ailleurs).
Paul A. Clayton
4

Un grand nombre de codes comporte de nombreux accès à la mémoire (30% est un chiffre typique). En dehors de cela, environ les 2/3 sont des accès en lecture et les 1/3 sont des accès en écriture. Cela n’est pas dû au manque de registres, mais aussi à l’accès aux tableaux, aux variables de membre d’objet, etc.

Cela doit être fait en mémoire (ou en cache de données) en raison de la fabrication du C / C ++ (tout ce que vous pouvez obtenir d’un pointeur doit avoir une adresse qui doit être potentiellement stockée en mémoire). Si le compilateur peut deviner que vous n'écrirez pas les variables à l'aide de superbes astuces de pointeur indirect, il les mettra dans des registres, ce qui convient parfaitement aux variables de fonction, mais pas aux variables globalement accessibles (généralement, tout ce qui sort de malloc). ()), car il est essentiellement impossible de deviner comment l’état global changera.

Pour cette raison, il n'est pas courant que le compilateur puisse faire quoi que ce soit avec plus de 16 registres d'utilisation générale de toute façon. C'est pourquoi tous les architectes populaires en ont à peu près autant (ARM en a 16).

Les MIPS et les autres RISC ont tendance à en avoir 32 car il n’est pas très difficile d’avoir autant de registres: le coût est suffisamment bas, c’est un peu un "pourquoi pas?". Plus de 32 sont pour la plupart inutiles et présentent l'inconvénient d'allonger l'accès au fichier de registre (chaque doublement du nombre de registres ajoute potentiellement une couche supplémentaire de multiplexeurs, ce qui ajoute un peu plus de retard ...). Cela rend également les instructions légèrement plus longues en moyenne - ce qui signifie que lorsque vous exécutez le type de programme qui dépend de la largeur de bande de la mémoire d'instructions, vos registres supplémentaires vous ralentissent!

Si votre unité centrale de traitement est en ordre et ne renomme pas les registres et que vous essayez d'effectuer beaucoup d'opérations par cycle (plus de 3), alors en théorie, vous avez besoin de davantage de registres à mesure que votre nombre d'opérations par cycle augmente. C'est pourquoi l'Itanium a tant de registres! Mais en pratique, mis à part le code orienté numériquement à virgule flottante ou orienté SIMD (pour lequel Itanium était vraiment bon), la plupart des codes auront beaucoup de lectures / écritures en mémoire et de sauts rendant impossible ce rêve de plus de trois opérations par cycle. (en particulier dans les logiciels orientés serveur comme les bases de données, les compilateurs, les exécutions de langages de haut niveau comme le javascript, les émulations, etc.). C'est ce qui a coulé l'Itanium.

Tout se résume à la différence entre le calcul et l'exécution!

Hubert Lamontagne
la source
2

Qui vous dit que le processeur a toujours 32 registres? x86 a 8, ARM 32 bits et x86_64 en ont 16, IA-64 en a 128 et bien d’autres. Vous pouvez jeter un oeil ici . Même les architectures MIPS, PPC ou toutes les architectures qui ont 32 registres à usage général dans le jeu d'instructions, le nombre est beaucoup plus grand que 32 puisqu'il y a toujours des registres d'indicateurs (le cas échéant), des registres de contrôle ... sans compter les registres renommés et les registres de matériel.

Tout a son prix. Plus le nombre de registres est important, plus vous avez du travail lors du changement de tâche, plus vous avez besoin d'espace pour coder les instructions. Si vous avez moins de registres, vous n'avez pas besoin de stocker et de restaurer beaucoup lorsque vous appelez et revenez de fonctions ou que vous changez de tâche sans avoir à remplacer l'absence de registres dans un code de calcul étendu.

En outre, plus le fichier de registre est volumineux, plus il sera coûteux et complexe. La mémoire SRAM étant la RAM la plus rapide et la plus chère, elle n’est utilisée que dans le cache du processeur. Mais cela reste beaucoup moins cher et prend moins de surface qu'un fichier de registre avec la même capacité.

phuclv
la source
2

Par exemple, un processeur Intel typique possède "officiellement" 16 registres entiers et 16 registres vectoriels. Mais en réalité, il y en a beaucoup plus: Le processeur utilise le "renommage de registre". Si vous avez une instruction reg3 = reg1 + reg2, vous rencontreriez un problème si une autre instruction utilisant reg3 n'avait pas encore fini. Vous ne pouvez pas exécuter la nouvelle instruction si elle écrase reg3 avant qu'elle ait été lue par l'instruction précédente.

Il y a donc environ 160 registres réels . Ainsi, l'instruction simple ci-dessus est remplacée par "regX = reg1 + reg2 et rappelez-vous que regX contient reg3". Sans registres de renom, une exécution déréglée serait absolument morte.

gnasher729
la source
1

Je ne suis pas un ingénieur électricien, mais je pense qu'une autre possibilité pour limiter le nombre de registres est le routage. Il y a un nombre limité d'unités arithmétiques et elles doivent être capables de prendre une entrée de chaque registre et une sortie vers chaque registre. Cela est particulièrement vrai lorsque vous avez des programmes en pipeline capables d'exécuter de nombreuses instructions par cycle.

O(n2)

J'ai eu l'idée de cette réponse en regardant quelques-uns des discours d'Ivan Godard sur le processeur de Mill. Une partie de l’innovation de la CPU Mill est que vous ne pouvez pas sortir sur des registres arbitraires - les sorties sont toutes placées dans une pile de registres ou "ceinture", ce qui réduit donc les problèmes de routage, car vous savez toujours où la sortie sera dirigée. Notez qu'ils ont toujours le problème de routage pour obtenir les registres d'entrée vers les unités arithmétiques.

Voir L'architecture CPU de Mill - The Belt (2 sur 9) pour l'énoncé du problème et la solution de Mill.

Salade de realz
la source
"Ils doivent être en mesure de prendre les entrées de chaque registre et de les envoyer à chaque registre." - J'imagine que cela est généralement implémenté avec un bus, il n'est pas nécessaire d'établir une connexion distincte avec les ALU pour chaque registre.
user253751
1
@immibis: Si vous souhaitez déplacer des données en 300 picosecondes, un bus ne le fera pas. Et si vous souhaitez déplacer beaucoup de données (par exemple, exécuter trois instructions avec deux opérandes et un résultat chacune dans le même cycle), un bus ne fonctionnera absolument pas.
gnasher729
0

Quant à la MIPS ISA, Hennessy et Patterson, 4ème édition de l' organisation et de la conception informatique p. 176, répond directement à cette question spécifique:

Plus petit est plus rapide. Le désir de rapidité est la raison pour laquelle MIPS dispose de 32 registres plutôt que de nombreux autres.

Olsoniste
la source