« Fonctionnement d'un ordinateur/Les jeux d'instructions » : différence entre les versions

Un livre de Wikilivres.
Contenu supprimé Contenu ajouté
Ligne 86 : Ligne 86 :
|[[File:Instruction Swap.png|Instruction Swap.]]
|[[File:Instruction Swap.png|Instruction Swap.]]
|}
|}

Enfin, on trouve des instructions pour sauvegarder la totalité de la pile ou de la file. Ces instructions servent surtout pour les appels de fonctions/sous-programmes, chacun d'entre eux demandant de vider complètement les registres du processeur, ainsi que la file ou la pile. Ils sont l'équivalent sur ces architectures des instructions de spilling, qui sauvegardent la totalité des registres du processeur en mémoire RAM, pour les récupérer à la fin de l'éxecution de la fonction.


===Les modes d'adressage des machines à pile/file===
===Les modes d'adressage des machines à pile/file===

Version du 2 mai 2018 à 22:48

Les instructions d'un processeur dépendent fortement du processeur utilisé. La liste de toute les instructions qu'un processeur peut exécuter s'appelle son jeu d'instructions. Celui-ci définit les instructions supportées, ainsi que la manière dont elles sont encodées en mémoire. Le jeu d'instruction de nos PC qui fonctionnent sous Windows est appelé le x86. C'est un jeu d'instructions particulièrement ancien, apparu certainement avant votre naissance : 1978. Depuis, de plus en plus d'instructions ont été ajoutées et rajoutées : ces instructions sont ce qu'on appelle des extensions x86. On peut citer par exemple les extensions MMX, SSE, SSE2, voir 3dnow!. Les anciens macintoshs (la génération de macintosh produits entre 1994 et 2006) utilisaient un jeu d'instruction différent : le PowerPC (depuis 2006, les macintoshs utilisent un processeur X86). Mais les architectures x86 et Power PC ne sont pas les seules au monde : il existe d'autres types d'architectures qui sont très utilisées dans le monde de l’informatique embarquée et dans tout ce qui est tablettes et téléphones portables derniers cris. On peut citer notamment l'architecture ARM, qui domine ce marché. Et n'oublions pas leurs consœurs MIPS et SPARC. Pour résumer, il existe différents jeux d'instructions, que l'on peut classer suivant divers critères.

Adressage des opérandes

La première classification que nous allons voir est celle basée sur l'adressage des opérandes, et les transferts de données entre mémoire, unité de calcul, et registres. Le critère de distinction entre ces architectures est les modes d'adressage autorisés pour les instructions de calcul. Les accès mémoire et branchements ne sont pas vraiment impliqués dans cette classification. La raison à cela est que les branchements ont des modes d'adressages dédiés, tandis que les accès mémoire ont besoin de beaucoup de modes d'adressage pour faire leur travail. Tel n'est pas le cas des instructions de calculs, qui peuvent utiliser un nombre limité de modes d'adressage sans le moindre problèmes. Certaines architectures ont décidé de limiter les modes d'adressages pour les calculs, afin de simplifier le processeur ou le travail du compilateur. D'autres ont décidé d'utiliser beaucoup de modes d'adressage, dans un souci de flexibilité ou de performances. Bref, voyons comment les différents types d'architectures ont géré le cas des modes d'adressages pour les instructions de calcul.

La classification que nous allons aborder distingue les jeux d'instructions par leur manière de gérer les opérandes. Dans les grandes lignes, on trouve trois catégories principales : les architectures mémoire-mémoire, les architectures à registres et les architectures FIFO/LIFO. Dans les architectures mémoire-mémoire, celles-ci sont lues directement depuis la mémoire RAM, sans intermédiaires. Pour les architectures à registres, le processeur stocke ses opérandes dans des registres. Enfin, les architectures à pile et à file utilisent une mémoire FIFO ou LIFO pour stocker les opérandes. Ces catégories sont souvent subdivisées en sous-catégories. Par exemple, les architectures à accumulateur peuvent être vues comme des architectures à registres particulières, dans lesquelles on trouve un unique registre pour les opérandes. Même chose pour les architectures LOAD-STORE, qui sont une sous-classe d'architectures à registres. Pour résumer, nous allons parler des architectures suivantes : les machines à pile, les machines à file, les architectures à accumulateur, les architectures mémoire-mémoire et les architectures à registres.

Classe d'architecture Intermédiaire entre la mémoire RAM et le reste du processeur
Architecture mémoire-mémoire Aucun. Les opérandes sont lues en RAM et les résultats y sont aussi écrits
Architecture à registres Registres. Les opérandes et résultats sont placés dans des registres.
Architecture à pile/à file Mémoire FIFO/LIFO. Les opérandes et résultats sont placés dans une mémoire FIFO ou LIFO.

Architecture mémoire-mémoire

Les toutes premières machines n'avaient pas de registres pour les données et ne faisaient que manipuler la mémoire RAM ou ROM : on parle d'architectures mémoire-mémoire. Dans cette architecture ci, il n'y a pas de registres généraux : les instructions n'accèdent qu'à la mémoire principale. Néanmoins, les registres d'instruction et pointeur d'instruction existent toujours. Les seules opérandes possibles pour ces processeurs sont des adresses mémoire, ce qui fait qu'un mode d'adressage est très utilisé : l'adressage absolu. Ces architectures avaient l'avantage d'avoir une gestion de la mémoire assez simple. Le nombre d'instruction d'accès mémoire était assez important, en raison de l'absence de registres, chose essentielle sur les architectures modernes. Ce genre d'architectures est aujourd'hui tombé en désuétude depuis que la mémoire est devenue très lente comparé au processeur.

Architecture mémoire-mémoire.
Architecture mémoire-mémoire.

Les architectures à registres

Les architectures mémoire-mémoire ont un défaut rédhibitoire : elles n'ont pas de registres pour stocker leurs opérandes. La conséquence est que les performances sont mauvaises, la RAM étant assez lente par rapport aux registres du processeur. Et encore une fois, les chercheurs et ingénieurs ont inventé des architectures qui résolvent ce problème : les architectures à registres. Celles-ci possèdent des registres qui permettent de conserver temporairement une opérande destinée à être utilisée souvent, ou des résultats de calculs temporaires. Il en existe plusieurs sous-types, qui se distinguent par leur nombre de registres pour les opérandes et par leurs modes d'adressages.

Architectures à accumulateur

Certains processeurs n'utilisent qu'un seul registre pour les données : l'accumulateur. Toute instruction arithmétique va lire un opérande depuis cet accumulateur, et y écrire son résultat. Si l'instruction a besoin de plusieurs opérandes, les opérandes qui ne sont pas dans l'accumulateur sont lus depuis la mémoire ou dans des registres séparés de l'accumulateur. Dans tous les cas, l'accumulateur est localisé grâce au mode d'adressage implicite. De plus, le résultat des instructions arithmétiques et logiques est stocké dans l'accumulateur, et on n'a pas besoin de préciser où stocker le résultat : pas de mode d'adressage pour le résultat. Par contre, les autres opérandes sont localisées avec d'autres modes d'adressage : absolu pour le cas le plus fréquent, inhérent (à registre) sur certaines architectures, indirect à registre pour d'autres, etc.

Architecture à accumulateur.
Architecture à accumulateur.

Historiquement, les premières architectures à accumulateur ne contenaient aucun autre registre : l'accumulateur était seul au monde. Pour faire ses calculs, notre processeur devait stocker une opérande dans l'accumulateur, et aller chercher les autres en mémoire. Sur ces processeurs, les modes d'adressages supportés étaient les modes d'adressages implicite, absolus, et immédiat. Ces architectures sont parfois appelées architectures 1-adresse, pour une raison simple : la majorité des instructions devait lire une opérande depuis la RAM. Il faut dire que la majorité des instructions d'un processeur n'a besoin que de deux opérandes et ne fournissent qu'un résultat : pensez aux instructions d'addition, de multiplication, de division, etc. Pour ces opérations, le résultat ainsi qu'une des opérandes sont stockés dans l'accumulateur, et adressés de façon implicite, seule la seconde opérande étant adressée directement.

Avec ces seuls modes d'adressages, l'utilisation de tableaux ou de structures devenait un véritable calvaire. Pour améliorer la situation, ces processeurs à accumulateurs ont alors incorporés des registres d'Index, capables de stocker des indices de tableaux, ou des constantes permettant de localiser une donnée dans une structure. Ces registres facilitaient donc les calculs d'adresses mémoire. Au départ, ces processeurs n'utilisaient qu'un seul registre d'Index, accessible et modifiable via des instructions spécialisées. Ce registre d'Index se comportait comme un second accumulateur, spécialisé dans les calculs d'adresses mémoire, adressé implicitement. Les modes d'adressages autorisés restaient les mêmes qu'avec une architecture à accumulateur normale. La seule différence, c'est que le processeur contenait de nouvelles instruction capables de lire ou d'écrire une donnée dans/depuis l'accumulateur, qui utilisaient ce registre d'Index de façon implicite. Mais avec le temps, nos processeurs finirent par incorporer plusieurs de ces registres. Nos instructions de lecture ou d'écriture devaient alors préciser quel registre d'Index utiliser. Le mode d'adressage Indexed Absolute vit le jour. Les autres modes d'adressages, comme le mode d'adressage Base + Index ou indirects à registres étaient plutôt rares à l'époque et étaient difficiles à mettre en œuvre sur ce genre de machines.

Ensuite, ces architectures s’améliorèrent un petit peu : on leur ajouta des registres capables de stocker des données. L’accumulateur n'était plus seul au monde. Mais attention : ces registres ne peuvent servir que d’opérande dans une instruction, et le résultat d'une instruction ira obligatoirement dans l'accumulateur. Ces architectures supportaient donc le mode d'adressage inhérent.

Les architectures à registres

Les processeurs à registres peuvent stocker temporairement des données dans des registres généraux ou spécialisés. Pour échanger des données entre la mémoire et les registres, on peut utiliser une instruction à tout faire : MOV. Sur d'autres, on utilise des instructions séparées pour copier une donnée de la mémoire vers un registre (LOAD), copier le contenu d'un registre dans un autre, copier le contenu d'un registre dans la mémoire RAM (STORE), etc.

Architecture à registres.
Architecture à registres.

Ces architectures à registres généraux (ainsi que les architectures Load-Store qu'on verra juste après) sont elles-même divisées en deux sous-classes bien distinctes : les architectures 2 adresses et les architectures 3 adresses. Cette distinction entre architecture 2 et 3 adresses permet de distinguer les modes d'adressages des opérations arithmétiques manipulant deux données : additions, multiplications, soustraction, division, etc. Ces instructions ont donc besoin d'adresser deux données différentes, et de stocker le résultat quelque part. Il leur faut donc préciser trois opérandes dans le résultat : la localisation des deux données à manipuler, et l'endroit où ranger le résultat. Sur les architectures à deux adresses, le résultat d'une instruction est stocké à l'endroit indiqué par la référence du premier opérande : cette donnée sera remplacée par le résultat de l'instruction. Avec cette organisation, les instructions ne précisent que deux opérandes. Mais la gestion des instructions est moins souple, vu qu'un opérande est écrasé. Avec cette organisation, les instructions sont plus courtes. Sur les architectures à trois adresses, on peut préciser le registre de destination du résultat. Ce genre d'architectures permet une meilleure utilisation des registres, mais les instructions deviennent plus longues que sur les architectures à deux adresses.

Les architectures LOAD-STORE

Les architectures LOAD-STORE sont identiques aux architectures à registres à un détail près : les instructions arithmétiques et logiques ne peuvent aller chercher leurs données que dans des registres du processeurs. Dit autrement, seules les instructions d'accès mémoire peuvent accéder à la mémoire, les autres instructions n’accédant pas directement à la mémoire. En conséquence, ces instructions ne peuvent prendre que des noms de registres ou des constantes comme opérandes : cela n'autorise que les modes d'adressage immédiat et à registre. Il faut noter aussi que les architectures Load-store sont elles aussi classées en architectures à 2 ou 3 adresses, comme les architectures à registres.

Architecture LOAD-STORE.
Architecture LOAD-STORE.

Architectures à pile et à file

Exemple d'une pile/file d'opérandes, chaque opérande étant codée sur 4 Bytes.

Les dernières architectures que nous allons voir sont les machines à pile et les machines à file des jeux d'instructions où les registres sont remplacés par une mémoire tampon dédiée aux opérandes de calculs. Il s'agit d'une mémoire de type FIFO pour une machine à file, alors que c'est une LIFO pour une machine à pile. La FIFO/LIFO est placée dans le processeur sur certaines machines, alors que d'autres la placent dans la mémoire RAM. Dans le premier cas, la mémoire tampon sert d'intermédiaire entre la mémoire RAM et le reste du processeur. Dans les deux cas, les opérandes sont stockées dans la mémoire tampon dans leur ordre d'ajout et chaque opérande prend exactement un byte, pas plus ni moins. Les opérandes sont chargées par certaines instructions dans la FIFO/LIFO et y sont placées les unes à la suite des autres. De plus, quand le processeur fait un calcul quelconque, le résultat est ajouté à la pile/file. La pile/file se remplit donc progressivement d'opérandes chargées volontairement ou de résultats de calculs. Les seules opérandes manipulables par une instruction sont placées au sommet de la pile ou file : il faut ajouter les opérandes unes par unes avant d'exécuter l'instruction. L'instruction dépile automatiquement les opérandes qu'elle utilise et empile son résultat.

Machines LIFO et FIFO.

Sur certaines machines FIFO/LIFO, la pile et la file sont localisées non pas dans le processeur, mais dans la mémoire RAM. Ces machines ont besoin d'un registre pour stocker l'adresse du sommet de la pile/file. Dans le cas d'une machine à pile, celui-ci est appelée le stack pointer, ou pointeur de pile. Sur les machines à file, il est appelé le queue pointer, ou encore le pointeur de file. Les instructions de calcul utilisent uniquement les registres de pile pour savoir où se trouvent les opérandes. En clair, toutes les instructions utilisent uniquement le mode d'adressage implicite. Il va de soit que pour simplifier la conception du processeur, celui-ci peut contenir des registres internes pour stocker temporairement les opérandes des calculs, mais ces registres ne sont pas accessibles au programmeur : ce ne sont pas des registres architecturaux. Par exemple, sur certaines machines à pile, tout ou partie de la pile est stockée directement dans des registres internes au processeur, pour gagner en performance.

Les instructions des machines à pile

Ces jeux d’instructions ont des instructions pour ajouter des opérandes dans la FIFO/LIFO ou en retirer. Dans ce qui va suivre, nous allons prendre l'exemple d'une machine à pile, mais il existe des équivalents pour les machines à file. Dans les grandes lignes, il y a deux instructions principales nommées PUSH et POP pour l'ajout et le retrait d'opérandes dans la pile, ainsi que quelques instructions annexes sur certaines machines. Les machines à file possèdent des instructions équivalentes, à savoir deux instructions Queue et Unqueue pour ajouter ou retirer une opérande dans la file, ainsi que quelques instructions annexes. Voici les deux instructions principales de gestion de la pile, présentes sur toutes les architectures à pile :

  • L'instruction PUSH permet d'empiler une donnée. Elle prend l'adresse de la donnée à empiler, charge la donnée, et met à jour le stack pointer.
  • L'instruction POP dépile la donnée au sommet de la pile, la stocke à l'adresse indiquée dans l'instruction, et met à jour le stack pointer.
Instruction Push. Instruction Pop.

Vu qu'une instruction dépile ses opérandes, on ne peut pas les réutiliser. Ceci dit, certaines instructions ont été inventées pour limiter la casse : on peut notamment citer l'instruction DUP, qui copie le sommet de la pile en deux exemplaires. On peut aussi citer l'instruction SWAP, qui échange deux données dans la pile.

Instruction Dup. Instruction Swap.

Enfin, on trouve des instructions pour sauvegarder la totalité de la pile ou de la file. Ces instructions servent surtout pour les appels de fonctions/sous-programmes, chacun d'entre eux demandant de vider complètement les registres du processeur, ainsi que la file ou la pile. Ils sont l'équivalent sur ces architectures des instructions de spilling, qui sauvegardent la totalité des registres du processeur en mémoire RAM, pour les récupérer à la fin de l'éxecution de la fonction.

Les modes d'adressage des machines à pile/file

Les machines à pile que je viens de décrire ne peuvent manipuler que des données sur la pile : ces machines à pile sont ce qu'on appelle des machines à zéro adresse. Avec une telle architecture, les instructions sont très petites, car il n'y a pas besoin de bits pour indiquer la localisation des données dans la mémoire, sauf pour POP et PUSH. Toutefois, certaines machines à pile plus évoluées autorisent certaines instructions à préciser l'adresse mémoire d'un (voire plusieurs, dans certains cas) de leurs opérandes. Ces machines sont appelées des machines à pile à une adresse.

En théorie, les instructions qui manipulent des opérandes ne peuvent qu’accéder aux instructions au sommet de la pile, ou la fin de la file. Ces opérandes sont retirées de la pile par l'instruction et le résultat est placé au sommet de la pile. C'est du moins le cas sur les machines à pile dites "pures", mais d'autres architectures ne fonctionnent pas ainsi. Avec elles, il est possible de préciser la position dans la pile des opérandes à utiliser. Une instruction peut donc indiquer qu'elle veut utiliser les opérandes situées 2, 3 ou 5 cases sous le sommet de la pile. Si les opérandes sélectionnées ne sont pas toujours retirées de la pile (tout dépend de l'architecture), le résultat est lui toujours placé au sommet de la pile. Les opérandes se déplacent donc bizarrement dans ce genre d'architectures. Ces jeux d'instruction n'utilisent pas la pile comme une mémoire LIFO, ce qui lui vaut le nom d'architecture à pseudo-pile. Il existe un équivalent pour les machines à file, qui portent encore une fois le nom de machines à file "impures" ou à pseudo-file.

Avantages et désavantages

Sur ces architectures, les programmes utilisent peu de mémoire. La raison à cela est que les instructions sont très petites : on n'a pas besoin d'utiliser de bits pour indiquer la localisation des données dans la mémoire, sauf pour Pop et Push. Vu que les programmes crées pour ces machines sont souvent très petits, on dit que la code density (la densité du code) est bonne. Par contre, une bonne partie des instructions de notre programmes seront des instructions Pop et Push qui ne servent qu'à déplacer les opérandes entre la RAM et la FIFO/LIFO. Une bonne partie des instructions ne sert donc qu'à manipuler la mémoire et pas à faire des calculs. Sans compter que notre programme comprendra beaucoup d'instructions comparé aux autres types de processeurs. Enfin, ces machines n'ont pas besoin d'utiliser beaucoup de registres pour stocker leur état : un Stack Pointer et un Program Counter suffisent. Les machines à pile furent les premières à être inventées et utilisées : dans les débuts de l’informatique, la mémoire était rare et chère, et l'économiser était important. Ces machines à pile permettaient d'économiser de la mémoire facilement et d'utiliser peu de registres, ce qui était le compromis idéal pour l'époque.

Les architectures à pile et à file ont plusieurs défauts. En premier lieu, la complexité de la gestion de la pile/file entraine l'usage d'un grand nombre d'instructions d'accès mémoire. Mais surtout, il est impossible de réutiliser une donnée chargée dans la pile, toute opération dépilant ses opérandes. Cela entraine un grand nombre d'accès en mémoire RAM, défaut rédhibitoire quand la RAM est lente et peu chère et où la hiérarchie mémoire dicte sa loi. Les autres classes d'architectures n'ont pas ces défauts et font usage de modes d'adressage autres que le mode d'adressage implicite. L'usage de ces modes d'adressages permet d'éviter d'avoir à copier des données dans une pile, les empiler, et les déplacer avant de les manipuler. Le nombre d'accès à la mémoire est donc plus faible comparé à une machine à pile pure.

RISC vs CISC

La seconde classification des jeux d'instructions que nous allons aborder se base sur le nombre d'instructions et classe nos processeurs en deux catégories :

  • les RISC (reduced instruction set computer), au jeu d'instruction simple ;
  • et les CISC (complex instruction set computer), qui ont un jeu d'instruction étoffé.
Propriété CISC RISC
Nombre d'instructions Élevé, parfois plus d'une centaine. Faible, moins d'une centaine.
Type d'instructions Beaucoup d'instructions complexes, qui prennent plus d'un cycle d'horloge : fonctions trigonométriques, gestion de texte, calculs cryptographiques, compression de données, etc. Pas d'instruction complexe : toutes les instructions prennent un cycle (hors accès mémoire).
Types de données Supportent des types de données complexes : texte via certaines instructions, graphes, listes chainées, etc. Types supportés limités aux entiers (adresses comprises) et flottants.
Modes d'adressage Gère un grand nombre de modes d'adressages : indirect avec auto-incrément/décrément, inhérent, indexed absolute, etc. Peu de modes d’adressage : les modes d'adressages complexes ne sont pas pris en charge.
Nombre d'accès mémoire par instruction Possibilité d'effectuer plusieurs accès mémoires par instruction, avec certains modes d'adressage. Pas plus d'un accès mémoire par instruction.
Load-store Non. Oui.
Spécialisation des registres Présence de registres spécialisés : registres flottants, registres d'adresses, registres d'index, registres pour les compteurs de boucle, etc. Presque pas de registres spécialisés, et usage de registres généraux.
Nombre de registres Peu de registres : rarement plus de 16 registres entiers. Beaucoup de registres, souvent plus de 32.
Longueur des instructions Instructions de taille variable, pour améliorer la densité de code. Instructions de taille fixe pour simplifier le processeur.

CISC

Les jeux d'instructions CISC sont les plus anciens : ils étaient à la mode jusqu'à la fin des années 1980. A cette époque, on programmait rarement avec des langages de haut niveau et beaucoup de programmeurs devaient utiliser l'assembleur. Avoir un jeu d'instruction complexe, avec des instructions de "haut niveau" qu'on ne devait pas refaire à partir d'instructions plus simples, était un gros avantage : cela facilitait la vie des programmeurs. Cette complexité des jeux d'instructions n'a pas que des avantages "humains", mais a aussi quelques avantages techniques, notamment en terme de densité de code : un programme codé sur CISC utilise moins d'instructions et prend donc moins de mémoire. A l'époque des processeurs CISC, la mémoire était rare et chère, ce qui faisait que les ordinateurs n'avaient pas plusieurs gigaoctets de mémoire : économiser celle-ci était crucial. Cet avantage était donc crucial, ce qui contrebalançait les défauts de ces architectures. Ces défauts étaient essentiellement le fait que le grand nombre d'instructions entraine une grande consommation de transistors et d'énergie. La difficulté de conception de ces processeur était aussi sans précédent.

RISC

Au fil du temps, on s'est demandé si les instructions complexes des processeurs CISC étaient vraiment utiles. Pour le programmeur qui écrit ses programmes en assembleur, elle le sont. Mais avec l'invention des langages de haut niveau, la roue a commencée à tourner. Diverses analyses ont alors étés effectuées par IBM, DEC et quelques chercheurs, visant à évaluer les instructions réellement utilisées par les compilateurs. Et à l'époque, les compilateurs n'utilisaient pas la totalité des instructions fournies par un processeur. Nombre de ces instructions n'étaient utilisées que dans de rares cas, voire jamais. Autant dire que beaucoup de transistors étaient gâchés à rien ! L'idée de créer des processeurs possédant des jeux d'instructions simples et contenant un nombre limité d'instructions très rapides commença à germer. Ces processeurs sont de nos jours appelés des processeurs RISC (acronyme de Reduced Instruction Set Computer). Ces processeurs RISC n'ont pas les défauts des CISC, mais n'en ont pas les avantages : la densité de code est mauvaise, en contrepartie d'une simplicité non-négligeable du processeur et une moindre consommation thermique/de transistors. Mais de tels processeurs RISC, complètement opposés aux processeurs CISC, durent attendre un peu avant de percer. Par exemple, IBM décida de créer un processeur possédant un jeu d'instruction plus sobre, l'IBM 801, qui fût un véritable échec commercial. Mais la relève ne se fit pas attendre. C'est dans les années 1980 que les processeurs possédant un jeu d'instruction simple devinrent à la mode. Cette année là, un scientifique de l'université de Berkeley décida de créer un processeur possédant un jeu d'instruction contenant seulement un nombre réduit d'instructions simples, possédant une architecture particulière. Ce processeur était assez novateur et incorporait de nombreuses améliorations qu'on retrouve encore dans nos processeurs haute performances actuels, ce qui fit son succès : les processeurs RISC étaient nés.

Conclusion

Durant longtemps, les CISC et les RISC eurent chacun leurs admirateurs et leurs détracteurs. De longs et interminables débats eurent lieu pour savoir si les CISC étaient meilleurs que les RISC, similaires aux "Windows versus Linux", ou "C versus C++", qu'on trouve sur n'importe quel forum digne de ce nom. Au final, on ne peut pas dire qu'un processeur CISC sera meilleur qu'un RISC ou l'inverse : chacun a des avantages et des inconvénients, qui rendent le RISC/CISC adapté ou pas selon la situation. Par exemple, on mettra souvent un processeur RISC dans un système embarqué, devant consommer très peu. Par contre, le CISC semble mieux adapté dans certaines conditions, en raison de la taille plus faible des programmes, ou quand les programmes peuvent faire un bon usage des instructions complexes du processeur. Au final, la performance d'un processeur dépend essentiellement d'un tas de paramètres, qui sont bien plus importants que le fait que le processeur soit un RISC ou un CISC.

De plus, de nos jours, les différences entre CISC et RISC commencent à s'estomper. Les processeurs actuels sont de plus en plus difficiles à ranger dans des catégories précises. Les processeurs actuels sont conçus d'une façon plus pragmatique : au lieu de respecter à la lettre les principes du RISC et du CISC, on préfère intégrer les techniques et instructions qui fonctionnent, peut importe qu'elles viennent de processeurs purement RISC ou CISC. Les anciens processeurs RISC se sont ainsi garnis d'instructions et techniques de plus en plus complexes et les processeurs CISC ont intégré des techniques provenant des processeurs RISC (pipeline, etc). La guerre RISC ou CISC n'a plus vraiment de sens de nos jours.

En parallèle de ces architectures CISC et RISC, qui sont en quelques sorte la base de tous les jeux d'instructions, d'autres classes de jeux d'instructions sont apparus, assez différents des jeux d’instructions RISC et CISC. On peut par exemple citer le Very Long Instruction Word, qui sera abordé dans les chapitre à la fin du tutoriel. La plupart de ces jeux d'instructions sont implantés dans des processeurs spécialisés, qu'on fabrique pour une utilisation particulière. Ce peut être pour un langage de programmation particulier, pour des applications destinées à un marche de niche comme les supercalculateurs, etc.

Jeux d'instructions spécialisés

En parallèle de ces architectures CISC et RISC, d'autres classes de jeux d'instructions sont apparus. Ceux-ci visent des buts distincts, qui changent suivant le jeu d'instruction :

  • soit ils cherchent à gagner en performance, en exécutant plusieurs instructions à la fois ;
  • soit ils sont adaptés à certaines catégories de programmes ou de langages de programmation ;
  • soit ils cherchent à diminuer la taille des programmes et à économiser de la mémoire ;
  • soit ils tentent d'améliorer la sécurité des programmes et les rendent résistants aux attaques.

Architectures parallèles

Certaines architectures sont conçues pour pouvoir exécuter plusieurs instructions en même temps, lors du même cycle d’horloge : elles visent le traitement de plusieurs instructions en parallèle, d'où leur nom d’architectures parallèles. Ces architectures visent la performance, et sont relativement généralistes, à quelques exceptions près. On peut, par exemple, citer les architectures very long instruction word, les architectures dataflow, les processeurs EDGE, et bien d'autres. D'autres instructions visent à exécuter une même instruction sur plusieurs données différentes : ce sont les instructions SIMD, vectorielles, et autres architectures utilisées sur les cartes graphiques actuelles. Nous verrons ces architectures plus tard dans ce tutoriel, dans les derniers chapitres.

Architectures dédiées

Certains jeux d'instructions sont dédiés à des types de programmes bien spécifiques, et sont peu adaptés pour des programmes généralistes. Parmi ces jeux d'instructions spécialisés, on peut citer les fameux jeux d'instructions Digital Signal Processor, aussi appelés des DSP. Nous reviendrons plus tard sur ces processeurs dans le cours, un chapitre complet leur étant dédié, ce qui fait que la description qui va suivre sera quelque peu succinte. Ces DSP sont des processeurs chargés de faire des calculs sur de la vidéo, du son, ou tout autre signal. Dès que vous avez besoin de traiter du son ou de la vidéo, vous avez un DSP quelque part, que ce soit une carte son ou une platine DVD. Ces DSP ont souvent un jeu d'instruction similaire aux jeux d'instructions RISC, avec quelques instructions supplémentaires spécialisées pour faire du traitement de signal. On peut par exemple citer l'instruction phare de ces DSP, l'instruction MAD, qui multiplie deux nombres et additionne un 3éme au résultat de la multiplication. De nombreux algorithmes de traitement du signal (filtres FIR, transformées de Fourier) utilisent massivement cette opération. Ces DSP possèdent aussi des instructions dédiées aux boucles, ou des instructions capables de traiter plusieurs données en parallèle (en même temps). De plus, les DSP utilisent souvent des nombres flottants assez particuliers qui n'ont rien à voir avec les nombres flottants que l'on a vu dans le premier chapitre. Certains DSP supportent des instructions capable d'effectuer plusieurs accès mémoire en un seul cycle d'horloge : ils sont reliés à plusieurs bus mémoire et sont donc capables de lire et/ou d'écrire plusieurs données simultanément. L'architecture mémoire de ces DSP est une architecture Harvard, couplée à une mémoire multi-ports. Les caches sont rares dans ces architectures, quoique parfois présents.

Certains processeurs sont carrément conçus pour un langage en particulier : par exemple, il existe des processeurs qui intègrent une machine virtuelle JAVA directement dans leurs circuits ! On appelle ces processeurs, conçus pour des besoins particuliers, des processeurs dédiés. Historiquement, les premiers processeurs de ce type étaient des processeurs dédiés au langage LISP, un vieux langage fonctionnel autrefois utilisé, mais aujourd'hui peu usité. De tels processeurs datent des années 1970 et étaient utilisés dans ce qu'on appelait des machines LISP. Ces machines LISP étaient capables d’exécuter certaines fonctions de base du langage directement dans leur circuits : elles possédaient notamment un garbage collector câblé dans ses circuits ainsi que des instructions machines supportant un typage déterminé à l’exécution. D'autres langages fonctionnels ont aussi eu droit à leurs processeurs dédiés : le prolog en est un bel exemple. Autre langage qui a eu l'honneur d'avoir ses propres processeurs dédiés : le FORTH, un des premiers langages à pile de haut niveau. Ce langage possède de nombreuses implémentations hardware et est un des rares langages de haut niveau à avoir été directement câblé en assembleur sur certains processeurs. Par exemple, on peut citer le processeur FC16, capable d’exécuter nativement du FORTH. En regardant dans les langages de programmation un peu plus connus, on peut aussi citer des processeurs spécialisés pour JAVA, qui intègrent une machine virtuelle JAVA directement dans leurs circuits : de quoi exécuter nativement du bytecode ! Certains processeurs ARM, qu'on trouve dans des système embarqués, sont de ce type.

Architectures compactes

Certains chercheurs ont inventé des jeux d’instruction pour diminuer la taille des programmes. Certains processeurs disposent de deux jeux d'instructions : un compact, et un avec une faible densité de code. Il est possible de passer d'un jeu d'instructions à l'autre en plein milieu de l’exécution du programme, via une instruction spécialisée. D'autres processeurs sont capables d’exécuter des binaires compressés (la décompression a lieu lors du chargement des instructions dans le cache).