La pratique en 2016 c'est que presque toutes les instructions consomment un cycle, voire une partie de cycle, on a assez de transistors pour cela. Donc on fait quoi ? On rajoute à nouveau des instructions : MMX, SSE (SIMD), AES... Alors CISC vs RISC, moi je rigole : ça n'existe plus.
Ça c'est faux. Les instructions vectorielles (AltiVec pour PowerPC, MMX/SSE/AVX pour x86) durent effectivement moins d'un cycle lorsque le pipeline est plein (donc en termes de « débit »). Mais la latence d'une instruction, fut-elle vectorielle, dure bien plus qu'un cycle.
De même, les instructions x86 scalaires de type « arithmétique avec opérandes à la fois en registre et en mémoire » nécessitent par définition plus d'un cycle pour s'exécuter, en particulier si on parle de divisions, modulo, ou opération flottantes.
Un jeu d'instruction RISC sépare explicitement les accès mémoire du reste : toutes les opérations arithmétiques se font registre à registre, là où les autres jeux d'instructions proposaient des systèmes plus ou moins complexes (calcul par accumulation, par pile, registre-mémoire, mémoire-mémoire, etc.; le x86 mélange tout ça).
La raison derrière le succès de RISC (et la raison pour laquelle Intel utilise un système de micro-opérations—AMD ne faisait [fait?] pas ça avec ses instructions x86 pour les premiers Athlon & Opteron), c'est justement qu'en réduisant le jeu d'instruction, et en le simplifiant,
On permet au pipeline d'être plus efficace : le débit en termes de cycles du pipeline est grosso-modo aussi rapide que le plus lent de ses étages. Donc si tous les étages de ton pipeline durent 1 cycle, sauf un étage qui en dure 5, ben t'es coincé avec un débit moyen par instruction de 5 cycles1
On permet au compilateur d'être plus efficace : lorsque le modèle machine est simple, la génération de séquences d'instruction est simplifiée. Lorsque le back-end du compilateur est mis à contribution, la plupart des algorithmes utilisés sont basés sur des heuristiques car les problèmes d'ordonnancement impliqués sont NP-complets.
Les algorithmes de sélection d'instruction doivent réussir à repérer des expressions dans la représentation intermédiaire du programme de manière à utiliser la meilleure instruction si possible. Par exemple, si j'ai une expression de type a = b + c * d, sur des machines x86 d'il y a 6 ans, il aurait fallu générer une multiplication et une addition séparément; sur les processeurs x86 récents, on peut utiliser une opération de Fused Multiply-Add (FMA) qui va se charger de tout. On y gagne en nombre d'instructions (une de moins) et en performance.
Le list scheduling (ordonnancement par liste) est un algorithme glouton qui se base sur la pondération de certaines contraintes pour choisir dans quel ordre émettre les instructions.
L'allocation de registres en utilisant un graphe d'interférence se base aussi sur des heuristiques, pour réduire le nombre de débordements (spills) qui forcent le programme à sauvegarder une valeur contenue dans un registre vers la mémoire et inversement.
Le principe de CISC, c'était de faire des instructions complexes qui utilisaient des registres cachés pour aller plus vite (et aussi gagner en espace mémoire). RISC, c'était un concept qui simplifiait le jeu d'instruction, demandait d'avoir beaucoup de registres adressables, et se reposait sur les progrès en théorie de la compilation pour correctement exploiter le pipeline (qui n'existait pas sur x86 avant le 486, et n'a commencé à être efficace qu'à partir du Pentium).
Je l'ai déjà dit ailleurs, un x86 (au moins chez Intel) aujourd'hui se comporte comme un RISC dès qu'on a passé le front-end (partie récupération d'instruction et décodage), vu que (1) toutes les instructions sont décomposées en micro-instructions, et (2) on est passé de ~8 registres adressables du Pentium 1 (32 bits) à 32 (16 « généraux » et 16 « vectoriels » mais qui peuvent être utilisés comme des vecteurs scalaires), comme les processeurs RISC classiques, sur les architectures 64 bits actuelles.
En attendant, le front-end du pipeline d'Intel reste limité, et la raison pour laquelle les instructions prennent en moyenne 1-2 cycles en termes de débit (hors accès mémoire), c'est qu'il y a en interne un cache de micro-instructions (à noter que le PowerPC/POWER d'IBM a un truc similaire, et pourtant le jeu d'instruction est bien plus proche de RISC que le x86).
Dans un univers de sources ouvertes ou il suffit de recompiler avec un compilateur adapté, tu peux avoir un set d'instruction qui reflète mieux l'organisation interne de ton CPU, et avoir au final une meilleure efficacité de ton CPU (et utilisation énergétique). D'où la percée des ARMs sur téléphones.
Oui et non : si ça reflète trop le comportement interne de ton processeur, alors il y a un vrai risque que le programmeur qui aurait besoin d'écrire de l'assembleur ait vraiment plus de mal à écrire son programme. La conception d'ISA doit être relativement fidèle à ce que fait le processeur, mais aussi doit permettre au concepteur de compilateur et au programmeur bas-niveau de pouvoir raisonner sur un modèle machine suffisamment complet mais aussi suffisamment simple. Exemple à la con : j'ai bossé (en tant que « programmeur ») sur une archi expérimentale qui faisait explicitement la différence entre nombres flottants et entiers (jusque là, pourquoi pas, ce ne serait pas la première fois), mais aussi entre les pointeurs « lointains » et « proches » (mémoire scratchpad locale ou de niveau supérieur, ce qui en soit n'est pas non plus complètement nouveau, ça se faisait avant aussi), et où il n'y avait aucun caches, donc tous les mouvements mémoire devaient être explicites. Franchement, écrire un compilateur optimisant pour ce truc, c'est dur.
Plus tard, lorsque l'architecture a été simplifiée du point de vue du logiciel, nous avions toujours besoin d'effectuer les mouvements mémoire manuellement, mais au moins le matériel avait intégré un système permettant de détecter la « distance » pour une opération mémoire (c'est important, vu que la raison première pour cette distinction était l'économie d'énergie: un accès « proche » pouvait ne demander que 16 bits, un accès moyen 32 bits, et un accès lointain ou absolu, 64 bits d'adresse).
Je passe sur tous les trucs et astuces qui existent pour sauter des étages, transmettre des résultats d'opérations à d'autres étages avant de les avoir effectivement écrits en mémoire/registre, etc. ↩
[^] # Re: Rien sur les spécificités du nouveau bébé 1er du top 500 ?
Posté par lasher . En réponse à la dépêche Le Top 500 des supercalculateurs de juin 2016. Évalué à 5.
Ça c'est faux. Les instructions vectorielles (AltiVec pour PowerPC, MMX/SSE/AVX pour x86) durent effectivement moins d'un cycle lorsque le pipeline est plein (donc en termes de « débit »). Mais la latence d'une instruction, fut-elle vectorielle, dure bien plus qu'un cycle.
De même, les instructions x86 scalaires de type « arithmétique avec opérandes à la fois en registre et en mémoire » nécessitent par définition plus d'un cycle pour s'exécuter, en particulier si on parle de divisions, modulo, ou opération flottantes.
Un jeu d'instruction RISC sépare explicitement les accès mémoire du reste : toutes les opérations arithmétiques se font registre à registre, là où les autres jeux d'instructions proposaient des systèmes plus ou moins complexes (calcul par accumulation, par pile, registre-mémoire, mémoire-mémoire, etc.; le x86 mélange tout ça).
La raison derrière le succès de RISC (et la raison pour laquelle Intel utilise un système de micro-opérations—AMD ne faisait [fait?] pas ça avec ses instructions x86 pour les premiers Athlon & Opteron), c'est justement qu'en réduisant le jeu d'instruction, et en le simplifiant,
a = b + c * d, sur des machines x86 d'il y a 6 ans, il aurait fallu générer une multiplication et une addition séparément; sur les processeurs x86 récents, on peut utiliser une opération de Fused Multiply-Add (FMA) qui va se charger de tout. On y gagne en nombre d'instructions (une de moins) et en performance.Le principe de CISC, c'était de faire des instructions complexes qui utilisaient des registres cachés pour aller plus vite (et aussi gagner en espace mémoire). RISC, c'était un concept qui simplifiait le jeu d'instruction, demandait d'avoir beaucoup de registres adressables, et se reposait sur les progrès en théorie de la compilation pour correctement exploiter le pipeline (qui n'existait pas sur x86 avant le 486, et n'a commencé à être efficace qu'à partir du Pentium).
Je l'ai déjà dit ailleurs, un x86 (au moins chez Intel) aujourd'hui se comporte comme un RISC dès qu'on a passé le front-end (partie récupération d'instruction et décodage), vu que (1) toutes les instructions sont décomposées en micro-instructions, et (2) on est passé de ~8 registres adressables du Pentium 1 (32 bits) à 32 (16 « généraux » et 16 « vectoriels » mais qui peuvent être utilisés comme des vecteurs scalaires), comme les processeurs RISC classiques, sur les architectures 64 bits actuelles.
En attendant, le front-end du pipeline d'Intel reste limité, et la raison pour laquelle les instructions prennent en moyenne 1-2 cycles en termes de débit (hors accès mémoire), c'est qu'il y a en interne un cache de micro-instructions (à noter que le PowerPC/POWER d'IBM a un truc similaire, et pourtant le jeu d'instruction est bien plus proche de RISC que le x86).
Oui et non : si ça reflète trop le comportement interne de ton processeur, alors il y a un vrai risque que le programmeur qui aurait besoin d'écrire de l'assembleur ait vraiment plus de mal à écrire son programme. La conception d'ISA doit être relativement fidèle à ce que fait le processeur, mais aussi doit permettre au concepteur de compilateur et au programmeur bas-niveau de pouvoir raisonner sur un modèle machine suffisamment complet mais aussi suffisamment simple. Exemple à la con : j'ai bossé (en tant que « programmeur ») sur une archi expérimentale qui faisait explicitement la différence entre nombres flottants et entiers (jusque là, pourquoi pas, ce ne serait pas la première fois), mais aussi entre les pointeurs « lointains » et « proches » (mémoire scratchpad locale ou de niveau supérieur, ce qui en soit n'est pas non plus complètement nouveau, ça se faisait avant aussi), et où il n'y avait aucun caches, donc tous les mouvements mémoire devaient être explicites. Franchement, écrire un compilateur optimisant pour ce truc, c'est dur.
Plus tard, lorsque l'architecture a été simplifiée du point de vue du logiciel, nous avions toujours besoin d'effectuer les mouvements mémoire manuellement, mais au moins le matériel avait intégré un système permettant de détecter la « distance » pour une opération mémoire (c'est important, vu que la raison première pour cette distinction était l'économie d'énergie: un accès « proche » pouvait ne demander que 16 bits, un accès moyen 32 bits, et un accès lointain ou absolu, 64 bits d'adresse).
Je passe sur tous les trucs et astuces qui existent pour sauter des étages, transmettre des résultats d'opérations à d'autres étages avant de les avoir effectivement écrits en mémoire/registre, etc. ↩