Pourquoi x86 est-il moche? Pourquoi est-il considéré inférieur par rapport aux autres?

Récemment, j’ai lu des archives SO et rencontré des instructions sur l’architecture x86.

et beaucoup plus de commentaires comme

  • “Comparé à la plupart des architectures, X86 ne craint pas vraiment.”

  • Il est indéniable que le X86 est inférieur à MIPS, SPARC et PowerPC

  • x86 est moche

J’ai essayé de chercher mais je n’ai trouvé aucune raison. Je ne trouve pas x86 mauvais probablement parce que c’est la seule architecture que je connais.

Quelqu’un peut-il gentiment me donner des raisons de considérer x86 moche / mauvais / inférieur par rapport aux autres.

Quelques raisons possibles:

  1. x86 est un ISA relativement ancien (ses progéniteurs étaient 8086, après tout)
  2. x86 a évolué de manière significative à plusieurs resockets, mais le matériel est nécessaire pour maintenir la rétrocompatibilité avec les anciens binarys. Par exemple, le matériel x86 moderne prend toujours en charge l’exécution du code 16 bits en mode natif. De plus, plusieurs modèles d’adressage de mémoire existent pour permettre aux anciens codes d’interagir sur le même processeur, tels que le mode réel, le mode protégé, le mode 8086 virtuel et le mode long (amd64). Cela peut être déroutant pour certains.
  3. x86 est une machine CISC. Pendant longtemps, cela a été plus lent que les machines RISC telles que MIPS ou ARM, car les instructions ont une interdépendance des données et des drapeaux rendant la mise en œuvre de la plupart des parallélismes de niveau d’instruction difficiles. Les implémentations modernes traduisent les instructions x86 en instructions de type RISC appelées « micro-ops » sous les couvertures pour rendre ce type d’optimisation pratique à implémenter dans le matériel.
  4. À certains égards, le x86 n’est pas inférieur, c’est juste différent. Par exemple, les entrées / sorties sont traitées en tant que mappage de mémoire sur la grande majorité des architectures, mais pas sur le x86. (NB: les machines x86 modernes prennent généralement en charge certaines formes de DMA et communiquent avec d’autres matériels via le mappage de mémoire, mais l’ ISA dispose toujours d’instructions d’ IN et de OUT telles que IN et OUT ).
  5. L’ ISA x86 possède un très petit nombre de registres d’architecture, ce qui peut forcer les programmes à parcourir la mémoire plus fréquemment que cela ne serait autrement nécessaire. Les instructions supplémentaires nécessaires pour cela requièrent des ressources d’exécution qui pourraient être consacrées à un travail utile, bien que le transfert en magasin efficace garde la latence faible. Les implémentations modernes avec un changement de nom de registre sur un fichier de registre physique volumineux peuvent conserver de nombreuses instructions en vol, mais l’absence de registres architecturaux constituait toujours une faiblesse significative pour le x86 32 bits. L’augmentation de x86-64 de 8 à 16 registres entiers et vectoriels est l’un des facteurs les plus importants du code 64 bits étant plus rapide que 32 bits (ainsi que l’ABI d’appel de registre plus efficace), pas la largeur accrue de chaque registre. Une augmentation supplémentaire de 16 à 32 registres entiers aiderait certains, mais pas autant. (AVX512 augmente à 32 registres vectoriels, cependant, car le code à virgule flottante a une latence plus élevée et nécessite souvent plus de constantes.) ( Voir commentaire )
  6. Le code assembleur x86 est compliqué car x86 est une architecture compliquée avec de nombreuses fonctionnalités. Une liste d’instructions pour une machine MIPS typique tient sur un morceau de papier de format lettre. La liste équivalente pour x86 remplit plusieurs pages, et les instructions ne font que faire plus, de sorte que vous avez souvent besoin d’une plus grande explication de ce qu’elles font qu’une liste peut fournir. Par exemple, l’ instruction MOVSB nécessite un bloc de code C relativement important pour décrire ce qu’il fait:

     if (DF==0) *(byte*)DI++ = *(byte*)SI++; else *(byte*)DI-- = *(byte*)SI--; 

    C’est une seule instruction qui fait un chargement, un magasin et deux ajouts ou soustractions (contrôlés par une entrée d’indicateur), chacun d’eux étant des instructions séparées sur une machine RISC.

    Bien que la simplicité de MIPS (et des architectures similaires) ne les rend pas nécessairement supérieures, pour enseigner une introduction à la classe de l’assembleur, il est logique de commencer par une ISA plus simple. Certaines classes d’assemblage enseignent un sous-ensemble ultra simplifié de x86 appelé y86 , qui est simplifié au-delà du sharepoint ne pas être utile pour un usage réel (par exemple, aucune instruction de décalage) ou certaines n’enseignent que les instructions x86 de base.

  7. Le x86 utilise des codes d’opération de longueur variable, ce qui ajoute une complexité matérielle en ce qui concerne l’parsing des instructions. Dans l’ère moderne, ce coût devient de plus en plus faible à mesure que les processeurs deviennent plus limités par la bande passante mémoire que par le calcul brut, mais de nombreux articles et attitudes «x86» viennent d’une époque où ce coût était beaucoup plus élevé.
    Mise à jour 2016: Anandtech a publié une discussion concernant les tailles d’opcode sous x64 et AArch64 .

EDIT: Ce n’est pas censé être une bash le x86! fête. Je n’avais pas d’autre choix que de faire un peu de dénigrement étant donné la formulation de la question. Mais à l’exception de (1), toutes ces choses ont été faites pour de bonnes raisons (voir les commentaires). Les concepteurs d’Intel ne sont pas stupides – ils voulaient réaliser certaines choses avec leur architecture, et c’est certaines des taxes qu’ils devaient payer pour que ces choses deviennent réalité.

Le principal choc contre x86 dans mon esprit est ses origines CISC – le jeu d’instructions contient beaucoup d’interdépendances implicites. Ces interdépendances compliquent la tâche de réorganisation des instructions sur la puce, car les artefacts et la sémantique de ces interdépendances doivent être préservés pour chaque instruction.

Par exemple, la plupart des instructions d’ajout et de soustraction d’entiers x86 modifient le registre de drapeaux. Après avoir effectué une addition ou une soustraction, l’opération suivante consiste souvent à regarder le registre des drapeaux pour vérifier le débordement, le bit de signe, etc. S’il y en a un autre, il est très difficile de savoir avant que le résultat du 1er ajout soit connu.

Sur une architecture RISC, l’instruction add spécifierait les opérandes d’entrée et le ou les registres de sortie, et tout ce qui concerne l’opération aurait lieu en utilisant uniquement ces registres. Cela rend beaucoup plus facile le découplage des opérations d’addition proches les unes des autres car il n’y a pas de registre de bloomin qui oblige tout à s’aligner et à exécuter un seul fichier.

La puce DEC Alpha AXP, une conception RISC de style MIPS, était douloureusement spartiate dans les instructions disponibles, mais le jeu d’instructions était conçu pour éviter les dépendances entre registres implicites entre instructions. Il n’y avait pas de registre de stack défini par le matériel. Il n’y avait pas de registre de drapeaux défini par le matériel. Même le pointeur d’instruction était défini par le système d’exploitation – si vous vouliez retourner à l’appelant, vous deviez déterminer comment l’appelant allait vous indiquer l’adresse à laquelle vous souhaitez revenir. Cela était généralement défini par la convention d’appel du système d’exploitation. Sur le x86, cependant, il est défini par le matériel de la puce.

Quoi qu’il en soit, sur 3 ou 4 générations de puces Alpha AXP, le matériel est passé d’une implémentation littérale du jeu d’instructions spartiates à 32 registres int et 32 ​​registres flottants à un moteur d’exécution massivement hors service avec 80 registres internes, le transfert de résultat (où le résultat d’une instruction précédente est transmis à une instruction ultérieure qui dépend de la valeur) et toutes sortes d’améliorations de performances folles et folles. Et avec tous ces avantages, la puce AXP était encore beaucoup plus petite que la puce Pentium comparable de l’époque, et l’AXP était bien plus rapide.

Vous ne voyez pas ces types de rafales de performances qui amplifient les choses dans l’arbre généalogique x86, en grande partie parce que la complexité du jeu d’instructions x86 rend de nombreux types d’optimisations d’exécution excessivement coûteux, voire impossibles. Le coup de génie d’Intel était de renoncer à la mise en œuvre du jeu d’instructions x86 dans le matériel – tous les processeurs x86 modernes sont en fait des kernelx RISC qui interprètent dans une certaine mesure les instructions x86 en les convertissant en sémantique du x86 d’origine. instruction, mais permet un peu de ce RISC désordonné et d’autres optimisations sur le microcode.

J’ai écrit beaucoup d’assembleurs x86 et je peux pleinement apprécier la commodité de ses racines CISC. Mais je n’appréciais pas à quel point x86 était compliqué jusqu’à ce que je passe du temps à écrire l’assembleur Alpha AXP. La simplicité et l’uniformité de l’AXP ont fait mouche. Les différences sont énormes et profondes.

L’architecture x86 date de la conception du microprocesseur 8008 et de ses apparentés. Ces processeurs ont été conçus à une époque où la mémoire était lente et si vous pouviez le faire sur le processeur, c’était souvent beaucoup plus rapide. Cependant, l’espace disque de l’unité centrale était également coûteux. Ces deux raisons expliquent pourquoi il existe un petit nombre de registres qui ont tendance à avoir des objectives spéciaux et un jeu d’instructions compliqué contenant toutes sortes de pièges et de limitations.

D’autres processeurs de la même époque (par exemple, la famille 6502) ont également des limitations et des bizarreries similaires. Fait intéressant, la série 8008 et la série 6502 étaient conçues comme des contrôleurs intégrés. Même à l’époque, les contrôleurs intégrés devaient être programmés en assembleur et, à bien des égards, pris en charge par le programmeur d’assemblage plutôt que par le rédacteur du compilateur. (Regardez la puce VAX pour savoir ce qui se passe lorsque vous écrivez au compilateur.) Les concepteurs ne s’attendaient pas à ce qu’ils deviennent des plates-formes informatiques polyvalentes; C’est ce à quoi servaient les prédécesseurs de l’archive POWER. La révolution de l’ordinateur à la maison a bien sûr changé la donne.

J’ai quelques aspects supplémentaires ici:

Considérez l’opération “a = b / c” x86 implémenterait ceci comme

  mov eax,b xor edx,edx div dword ptr c mov a,eax 

Comme bonus supplémentaire de l’instruction div, edx contiendra le rest.

Un processeur RISC nécessiterait d’abord de charger les adresses de b et de c, de charger b et c de la mémoire dans les registres, de faire la division et de charger l’adresse de a puis de stocker le résultat. Dst, syntaxe src:

  mov r5,addr b mov r5,[r5] mov r6,addr c mov r6,[r6] div r7,r5,r6 mov r5,addr a mov [r5],r7 

Ici, il n’y aura généralement pas de rest.

Si des variables doivent être chargées via des pointeurs, les deux séquences peuvent devenir plus longues, bien que cela soit moins possible pour le RISC, car il peut avoir un ou plusieurs pointeurs déjà chargés dans un autre registre. x86 a moins de registre, donc la probabilité que le pointeur se trouve dans l’un d’eux est plus petite.

Avantages et inconvénients:

Les instructions RISC peuvent être mélangées avec le code environnant pour améliorer la planification des instructions, ce qui est moins possible avec x86, qui fait ce travail (plus ou moins bien selon la séquence) dans le processeur lui-même. La séquence RISC ci-dessus aura généralement une longueur de 28 octets (7 instructions de 32 bits / 4 octets chacune) sur une architecture 32 bits. La mémoire hors puce fonctionnera davantage lors de l’extraction des instructions (sept extractions). La séquence x86 plus dense contient moins d’instructions et, bien que leur largeur varie, vous recherchez probablement une moyenne de 4 octets / instruction. Même si vous avez des caches d’instructions pour accélérer ces sept récupérations, cela signifie que vous aurez un déficit de trois ailleurs par rapport au x86.

L’architecture x86 avec moins de registres à enregistrer / restaurer signifie qu’elle effectuera probablement des changements de thread et gérera les interruptions plus rapidement que RISC. Davantage de registres à enregistrer et à restaurer nécessite un espace de stack RAM plus temporaire pour effectuer des interruptions et un espace de stack plus permanent pour stocker les états des threads. Ces aspects devraient faire de x86 un meilleur candidat pour exécuter du RTOS pur.

Sur une note plus personnelle, je trouve plus difficile d’écrire un assemblage RISC que x86. Je résous ce problème en écrivant la routine RISC dans C, en compilant et en modifiant le code généré. Ceci est plus efficace du sharepoint vue de la production de code et probablement moins efficace du sharepoint vue de l’exécution. Tous ces 32 registres à suivre. Avec x86, c’est l’inverse: 6-8 registres avec des noms “réels” rendent le problème plus facile à gérer et instaure plus de confiance dans le fait que le code produit fonctionnera comme prévu.

Laid? C’est dans l’oeil du spectateur. Je préfère “différent”

Je pense que cette question a une fausse hypothèse. Ce sont principalement les universitaires obsédés par RISC qui appellent x86 moche. En réalité, l’ISA x86 peut faire en une seule opération des instructions qui prendront 5 à 6 instructions sur les ISA RISC. Les ventilateurs RISC peuvent contrer le fait que les processeurs x86 modernes décomposent ces instructions “complexes” en microops; toutefois:

  1. Dans de nombreux cas, ce n’est que partiellement vrai ou pas du tout vrai. Les instructions “complexes” les plus utiles dans x86 sont des choses comme mov %eax, 0x1c(%esp,%edi,4) c’est-à-dire les modes d’adressage, et ceux-ci ne sont pas décomposés.
  2. Ce qui est souvent plus important sur les machines modernes, ce n’est pas le nombre de cycles utilisés (car la plupart des tâches ne sont pas liées au processeur) mais l’impact du code sur le cache d’instruction. 5-6 instructions de taille fixe (généralement 32 bits) auront un impact sur le cache de plus d’une instruction complexe qui dépasse rarement 5 octets.

Le x86 a vraiment absorbé tous les aspects positifs de RISC il y a environ 10 à 15 ans, et les qualités restantes de RISC (en fait la définition – le jeu d’instructions minimal) sont nuisibles et indésirables.

Outre le coût et la complexité de la fabrication des processeurs et de leurs besoins en énergie, le x86 est le meilleur ISA . Quiconque vous dit le contraire laisse l’idéologie ou l’agenda entraver son raisonnement.

D’un autre côté, si vous ciblez des appareils embarqués où le coût du processeur est élevé, ou des appareils embarqués / mobiles où la consommation d’énergie est une préoccupation majeure, ARM ou MIPS est probablement plus logique. Gardez à l’esprit que vous devrez toujours gérer la taille supplémentaire du binary et du binary nécessaire pour gérer le code facilement 3 à 4 fois plus gros, et vous ne pourrez pas vous rapprocher des performances. Que cela compte dépend en grande partie de ce que vous allez faire.

Le langage d’assembleur x86 n’est pas si mal. C’est quand vous arrivez au code machine qu’il commence à devenir vraiment moche. Les codages d’instructions, les modes d’adressage, etc. sont beaucoup plus compliqués que ceux de la plupart des processeurs RISC. Et il y a du plaisir supplémentaire à des fins de compatibilité descendante – des choses qui ne se déclenchent que lorsque le processeur est dans un certain état.

Dans les modes 16 bits, par exemple, l’adressage peut sembler carrément bizarre; il y a un mode d’adressage pour [BX+SI] , mais pas un pour [AX+BX] . Des choses comme celles-là ont tendance à compliquer l’utilisation des registres, car vous devez vous assurer que votre valeur est dans un registre que vous pouvez utiliser à votre guise.

(Heureusement, le mode 32 bits est beaucoup plus sûr (même s’il rest un peu étrange à certains moments – segmentation par exemple), et le code x86 16 bits n’est plus pertinent en dehors des chargeurs de démarrage et de certains environnements intégrés.)

Il y a aussi les rests des temps anciens, quand Intel essayait de faire du x86 le processeur ultime. Des instructions longues de quelques octets qui exécutaient des tâches que personne ne fait plus, car elles étaient franchement trop floues, lentes ou compliquées. Les instructions ENTER et LOOP , pour deux exemples – notez que le code du frame C stack est comme “push ebp; mov ebp, esp” et non “enter” pour la plupart des compilateurs.

Je ne suis pas un expert, mais il semble que bon nombre des raisons pour lesquelles les gens n’aiment pas cela peuvent être les raisons pour lesquelles il fonctionne bien. Il y a plusieurs années, avoir des registres (au lieu d’une stack), enregistrer des frameworks, etc. étaient considérés comme de bonnes solutions pour rendre l’architecture plus simple pour les humains. Cependant, de nos jours, ce qui compte, ce sont les performances du cache, et les mots de longueur variable de x86 lui permettent de stocker plus d’instructions dans le cache. Le “décodage des instructions”, qui, à mon avis, a été remarqué une fois que les adversaires ont pris la moitié de la puce, n’a plus autant de sens.

Je pense que le parallélisme est l’un des facteurs les plus importants de nos jours – du moins pour les algorithmes qui fonctionnent déjà assez vite pour être utilisables. L’expression d’un parallélisme élevé dans le logiciel permet au matériel d’amortir (ou souvent de cacher complètement) les latences de mémoire. Bien entendu, le futur de l’architecture atteint probablement un point tel que l’informatique quantique.

NVidia m’a dit que l’une des erreurs d’Intel était de garder les formats binarys proches du matériel. Le PTX de CUDA effectue des calculs rapides d’utilisation des registres (coloration des graphes), de sorte que nVidia peut utiliser une machine de registre au lieu d’une stack, tout en conservant un chemin de mise à niveau qui ne casse pas tous les anciens logiciels.

Je pense que vous obtiendrez une partie de la réponse si vous essayez d’écrire un compilateur qui cible x86, ou si vous écrivez un émulateur de machine x86, ou même si vous essayez d’implémenter ISA dans une conception matérielle.

Bien que je comprenne le “x86 est moche!” arguments, je pense toujours que c’est plus amusant d’ écrire un assemblage x86 que MIPS (par exemple) – ce dernier est tout simplement fastidieux. Il était toujours destiné à être agréable aux compilateurs plutôt qu’aux humains. Je ne suis pas sûr qu’une puce puisse être plus hostile aux auteurs du compilateur si elle essayait …

La partie la plus laide pour moi est la façon dont la segmentation (en mode réel) fonctionne – que toute adresse physique a un segment 4096: les alias de décalage. Quand avez-vous eu besoin de ça la dernière fois? Les choses auraient été tellement plus simples si la partie de segment était ssortingctement des bits d’ordre supérieur d’une adresse 32 bits.

Outre les raisons que les gens ont déjà mentionnées:

  • x86-16 avait un schéma d’adressage de la mémoire plutôt étrange qui permettait d’ adresser un seul emplacement mémoire de 4096 manières différentes, de limiter la mémoire vive à 1 Mo, et obligeait les programmeurs à gérer deux tailles de pointeurs différentes. Heureusement, le passage à 32 bits rend cette fonctionnalité inutile, mais les puces x86 conservent toujours les registres des segments.
  • Bien que ce ne soit pas une faute de x86 en tant que tel , les conventions d’appel x86 n’étaient pas normalisées comme le faisait MIPS (principalement parce que MS-DOS ne __cdecl aucun compilateur), ce qui nous laissait le désordre de __cdecl , __stdcall , __fastcall , etc.
  1. x86 possède un ensemble très limité de registres à usage général

  2. il favorise un style de développement très inefficace au plus bas niveau (l’enfer de l’ICCA) au lieu d’une méthodologie efficace de chargement / stockage

  3. Intel made the horrifying decision to introduce the plainly stupid segment / offset – memory adressing model to stay compatible with (at this time already!) outdated technology

  4. At a time when everyone was going 32 bit, the x86 held back the mainstream PC world by being a meager 16 bit (most of them – the 8088 – even only with 8 bit external data paths, which is even scarier!) CPU


For me (and I’m a DOS veteran that has seen each and every generation of PCs from a developers perspective!) point 3. was the worst.

Imagine the following situation we had in the early 90s (mainstream!):

a) An operating system that had insane limitations for legacy reasons (640kB of easily accessible RAM) – DOS

b) An operating system extension (Windows) that could do more in terms of RAM, but was limited when it came to stuff like games, etc… and was not the most stable thing on Earth (luckily this changed later, but I’m talking about the early 90s here)

c) Most software was still DOS and we had to create boot disks often for special software, because there was this EMM386.exe that some programs liked, others hated (especially gamers – and I was an AVID gamer at this time – know what I’m talking about here)

d) We were limited to MCGA 320x200x8 bits (ok, there was a bit more with special sortingcks, 360x480x8 was possible, but only without runtime library support), everything else was messy and horrible (“VESA” – lol)

e) But in terms of hardware we had 32 bit machines with quite a few megabytes of RAM and VGA cards with support of up to 1024×768

Reason for this bad situation?

A simple design decision by Intel. Machine instruction level (NOT binary level!) compatibility to something that was already dying, I think it was the 8085. The other, seemingly unrelated problems (graphic modes, etc…) were related for technical reasons and because of the very narrow minded architecture the x86 platform brought with itself.

Today, the situation is different, but ask any assembler developer or people who build comstackr backends for the x86. The insanely low number of general purpose registers is nothing but a horrible performance killer.