Introduction : L'influence permanente du CDCI sur les compilateurs modernes

La relation entre l'architecture du processeur et l'optimisation des logiciels est une pierre angulaire de l'informatique. Parmi les paradigmes architecturaux les plus pertinents, on trouve le complexe de calcul de l'instruction (CISC), une philosophie de conception qui a façonné le développement du compilateur pendant des décennies. Contrairement à son homologue, le calcul de l'instruction réduite (RISC), qui repose sur un petit ensemble d'instructions rapides et simples, les processeurs du CISC sont riches en opérations multi-étapes, telles que la copie de chaîne, l'évaluation polynomiale ou l'arithmétique mémoire-à-mémoire, dans des instructions de machine individuelles.

Cet article explore l'impact profond de la conception du CDCI sur les stratégies d'optimisation du compilateur. Nous disséquerons les domaines clés, y compris la sélection des instructions, la densité de code, la fusion de macro-opérations, l'attribution des registres sous des longueurs d'instruction variables, et les défis modernes posés par la décomposition micro-op du CDCI.

Bref historique du CDCI : des images principales à x86

Pour réduire le nombre d'instructions nécessaires à un programme donné, les architectes ont emballé plus de fonctionnalités dans chaque instruction. IBM System/360, introduit en 1964, est un exemple fondamental : son ensemble d'instructions comprenait l'arithmétique des valeurs en mémoire, les branches conditionnelles avec des codes d'état multiples, et les opérations de haut niveau comme -Compare et Branch. Cette philosophie de conception a continué avec la Digital Equipment Corporation , architecture VAX (1977), qui a vanté plus de 300 instructions, beaucoup capables d'effectuer des mouvements complexes de données et l'arithmétique en une seule opération [Comer, - -The VAX Architecture , -

La famille la plus durable du CISC est l'architecture x86, qui a été créée avec l'Intel 8086 en 1978. x86 , l'ensemble d'instructions a évolué à travers des extensions comme MMX, SSE et AVX, accumulant des centaines d'instructions qui varient de longueur sauvage (1 à 15 octets). Malgré la révolution du RISC des années 1980 – qui a prouvé que des instructions plus simples pouvaient produire des vitesses d'horloge plus élevées et une pipeline plus facile – le CISC est resté dominant sur les marchés des ordinateurs de bureau et des serveurs en raison de la compatibilité arrière et d'une vaste base logicielle installée.

Stratégies d'optimisation des compilateurs ayant un impact sur le CDCI

La richesse d'un ensemble d'instructions du CDCI crée des opportunités et des défis pour les compilateurs. Ci-dessous, nous examinons les domaines clés où la conception du CDCI conduit les décisions d'optimisation.

Sélection de l'instruction: Équilibrer la puissance et le coût

Dans un système RISC, la sélection des instructions est relativement simple : le compilateur mapait les opérations de haut niveau à un petit ensemble d'instructions simples, en s'appuyant sur l'optimiseur pour fusionner des séquences là où cela est bénéfique. Dans le CISC, le compilateur doit choisir parmi un vaste menu d'instructions, chacune avec une longueur, une latence et une utilisation des ressources différentes. Par exemple, pour calculer , un compilateur RISC peut générer trois instructions (multiplier, ajouter, stocker).

Les compilateurs modernes (GCC, LLVM) utilisent des modèles de correspondance de patrons et de coûts pour prendre ces décisions. Le moteur spécifique à la cible (p. ex. x86=s dans LLVM) contient des centaines de modèles qui sélectionnent la meilleure séquence d'instruction pour un modèle IR donné. Par exemple, lorsqu'une boucle contient une séquence de charges mémoire et un ajout, le compilateur peut choisir un mode d'adressage indexé (p. ex. ) pour effectuer le calcul de l'adresse et le fonctionnement de la mémoire dans une instruction. Cela réduit le nombre d'instructions mais introduit la complexité : le compilateur doit s'assurer que le calcul de l'adresse ne déborde pas ou cause une erreur de segmentation.

Densité et utilisation des caches

Un des avantages historiques du CDCI est la densité de code. Comme une seule instruction du CDCI peut remplacer plusieurs instructions RISC, le binaire résultant est souvent plus petit. Par exemple, une instruction du CDCI qui charge à partir d'une adresse mémoire en utilisant un décalage 32 bits ne prend que 5 à 7 octets, tandis que la séquence RISC équivalente (charger l'adresse dans le registre, puis charger à partir du registre) peut nécessiter 8 à 12 octets.

Les compilateurs exploitent la densité de code par des techniques comme:

  • Shortening d'instruction:[ Lorsque cela est possible, le compilateur choisit le plus petit encodage (p. ex., en utilisant au lieu de avec un effet immédiat de 32 bits si la valeur correspond à 8 bits). Les encodages du CDCI de longueur variable (x86-64) permettent même un formulaire de 2 octets pour des instructions communes.
  • L'attribution de la pile par rapport au registre:[ Dans le code du CDCI profondément imbriqué, les compilateurs se déposent parfois sur la pile en utilisant des instructions de poussée/pop compactes (/ dans la x86 ne sont que 1 octet chacun) plutôt que des mouvements génériques avec la mémoire de registre qui prennent 3–4 octets.
  • Utilisation de modes d'adressage complexes :[ Le mode d'adressage indexé ([) permet une seule instruction de charger à partir d'un élément de tableau. Les compilateurs évaluent soigneusement si l'encodage plus long (jusqu'à 7 octets) est compensé par l'élimination d'une instruction de calcul d'adresse séparée.

Cependant, l'augmentation de la densité de code n'améliore pas toujours les performances. Des instructions plus longues peuvent prendre plus de temps à décoder (surtout au début des pipelines x86), et l'encodage de longueur variable rend le prédécodage et la prédiction des branches plus difficiles.

Fusion macro-opérationnelle et décomposition micro-op

Les processeurs modernes du CDCI (x86 depuis Pentium M vers l'intérieur) brisent les instructions complexes en micro-opérations simples (μops) qui se plantent sur le pipeline d'exécution. Par exemple, un x86 est décomposé en une charge μop, un arithmétique μop et un stockage μop. Cette décomposition permet au processeur de garder le pipeline plein et d'exploiter l'exécution hors de l'ordre, mais cela signifie également qu'une seule instruction du CDCI peut apparaître au moteur d'exécution comme trois opérations distinctes.

Les compilateurs doivent rendre compte de cette micro-architecture. Deux stratégies clés sont apparues :

  • Macrofusion: Certaines instructions du CDCI combinent deux opérations logiques (par exemple, comparaison et branche). Sur x86, certaines paires comme suivies de sont fusionnées par le processeur en une seule μop. Le compilateur peut encourager la fusion en maintenant la comparaison et la branche adjacente et en évitant les instructions qui modifient les codes de condition entre eux. GCC et LVM comprennent des passes d'ordonnances spécifiques qui organisent des instructions pour maximiser la macrofusion.
  • Micro-op cache: Les noyaux récents x86 (Intel Haswell et plus tard) incluent un cache μop qui stocke les μops décodés pour les boucles. Pour exploiter cela, les compilateurs génèrent du code qui correspond à la taille de la ligne de cache μop (souvent 4–6 μops). Ils alignent également les en-têtes de boucles sur les limites de la ligne de cache.

Il est intéressant de noter que la décomposition micro-op permet parfois de décoder des instructions plus simples comme celles du RISC que celles de leur équivalent du CISC. Par exemple, une séquence de et utilisant des registres peut être décodée en moins de μops totaux qu'un seul qui consomme trois μops. Les compilateurs modernes utilisent des modèles de coûts qui simulent le nombre de μops, la latence et l'utilisation du port pour choisir la meilleure séquence.

Inscription des affectations et des durées d'instruction variables

L'attribution des registres est compliquée par le CDCI car de nombreuses instructions peuvent accéder directement à la mémoire, ce qui rend la pression des registres moins critique, mais aussi introduire des compromis. Lorsqu'un compilateur attribue un registre à une variable fréquemment utilisée, il peut éviter les opérations de mémoire, mais les instructions de registre à registre résultant sont généralement plus longues (en raison d'octets modificateurs) que les versions d'accès à la mémoire. Par exemple, (avec un octet ModRM) est de 2 à 4 octets, alors que n'est que de 2 octets.

Les compilateurs du CDCI doivent évaluer l'avantage de conserver une valeur dans un registre contre la possibilité d'augmenter la taille du code et de décoder la latence. Ils utilisent souvent l'heuristique en fonction de la profondeur de la boucle et de la taille de la fonction. Par exemple, dans une boucle chaude, le compilateur favorisera les registres pour éviter la latence de la mémoire, même si cela signifie utiliser des encodages d'instruction plus longs.

Un autre défi est le nombre limité de registres à usage général en x86 : seulement 8 en mode 32 bits (EAX, EBX, ECX, EX, ESI, EDI, EBP, ESP) et 16 en mode 64 bits. Cette rareté oblige les compilateurs à être intelligents dans l'attribution des registres. De nombreuses instructions du CDCI ont une utilisation implicite des registres (p. ex. utilise implicitement EAX et EDX), ce qui limite l'allocateur. Les compilateurs modernes utilisent des allocataires de couleur graphe avec des contraintes spécifiques à la cible (p. ex., - n'assigner pas EAX pour cette valeur parce qu'elle sera cambriolée par la division suivante).

Défis posés par la complexité du CDCI

Bien que le CDCI offre de nombreuses possibilités d'optimisation, il introduit également des obstacles importants pour les rédacteurs de compilation.

Calendrier de l'instruction et latence variable

Dans les architectures RISC, la plupart des instructions ont une latence prévisible et uniforme (souvent un cycle pour les opérations simples de l'ALU). Les instructions du CDCI peuvent avoir des latences très variables. Par exemple, un simple peut prendre un cycle, tandis qu'un (division entière) prend 20 à 40 cycles. Même la même instruction peut avoir des latences différentes selon les types d'opérations (registre vs mémoire) et l'alignement. Cela rend la programmation statique extrêmement complexe. Les compilateurs comptent souvent sur des tables d'instruction (p. ex., tables de référence pour l'optimisation Intel) qui dressent des latences, des débits et des utilisations portuaires pour chaque variante d'instruction.

Complexité de l'optimisation du trou de profondeur

Par exemple, une séquence comme peut être remplacée par un seul si le compilateur vérifie que les drapeaux de l'état ne sont pas utilisés ailleurs. Cette transformation permet d'économiser deux instructions et réduit la pression du registre. Cependant, le modèle doit être sûr : l'emplacement de la mémoire peut être consulté par un autre fil ou un pseudonyme avec un pointeur différent. Les compilateurs doivent effectuer une analyse précise des alias pour appliquer ces trous. L'ISA x86 comprend également de nombreuses instructions qui ont des effets secondaires implicites (p. ex. modifie EDI et EFLAGS), ce qui rend risqué de remplacer sans connaissance approfondie.

Les cartes LLVM et GCC modernes ont des passages de trous d'entrée étendus qui fonctionnent pendant le moteur spécifique à la cible. Par exemple, les cartes LLVM remplacent certains modèles de bas niveau par des instructions plus efficaces du CDCI. Ce passage est heuristique et doit être soigneusement entretenu, car les micro-architectures de processeurs introduisent différents compromis.

Puissance et considérations thermiques

Bien que ce ne soit pas un problème de compilateur en soi, la consommation d'énergie est de plus en plus importante. Les instructions du CDCI qui relient plusieurs unités d'exécution (p. ex. qui fusionne multi-add) peuvent causer des pics de puissance dynamiques élevés. Les compilateurs ciblant les processeurs mobiles et intégrés x86 (comme Intel Atom) évitent parfois de telles instructions de puissance pour des opérations plus simples, même si cela augmente la taille du code. La décision est prise dans le pipeline d'optimisation, souvent par l'intermédiaire d'un modèle de coût spécifique à la cible qui inclut un budget d'énergie.

Possibilités : tirer parti du CDCI pour obtenir des gains de rendement

Malgré la complexité, l'ensemble riche d'instructions du CISC offre des possibilités d'optimisation uniques que le RISC ne peut souvent pas assortir.

Instructions spécialisées pour les charges de travail en cryptographie et en médias

Les familles du CDCI comme x86 ont accumulé un vaste éventail d'instructions spécialisées.

  • AES-NI: , ], et les instructions connexes accélèrent les opérations de cryptage avancé standard. Les compilateurs peuvent reconnaître les boucles qui effectuent des tours AES et les remplacer par ces instructions uniques, en obtenant des facteurs de 10-20x de accélération par rapport aux implémentations logicielles [Guide d'optimisation Intel AES‐NI].
  • Extensions SHA: et d'autres accélèrent les algorithmes de hachage.
  • AVX-512: La détection multiplicative, scatter/collecter et les conflits peuvent accélérer considérablement le HPC et le code vectorisé. Les compilateurs utilisent des passes de vectorisation automatique pour générer ces instructions, souvent avec des vérifications d'exécution pour le support CPU.
  • BMI/BMI2:[ Les instructions de manipulation bit (p. ex. , ) permettent une implémentation compacte de certaines opérations bit-field. Les compilateurs pour la base de données et le code de réseau peuvent remplacer automatiquement les boucles par ces instructions.

Pour exploiter ces fonctions, les compilateurs doivent connaître le jeu de fonctionnalités CPU. LLVM et GCC utilisent des vérifications CPUID et des annotations d'attributs spécifiques à la cible (comme . Dans le réglage de plusieurs parties, le compilateur peut générer plusieurs chemins de code et sélectionner celui qui convient à l'exécution par multiversion de fonction.

Compatibilité du code héritage et réécriture binaire

Pour les optimisations de compilateur, cela signifie que le code d'objet existant des plus anciens compilateurs peut parfois être amélioré par des outils de réécriture binaire (par exemple, l'outil PIN Intel , ou des optimisateurs automatiques comme BOLT). Ces outils effectuent des optimisations de dernière minute que les compilateurs ne peuvent pas facilement faire parce qu'ils manquent d'informations sur l'exécution. Par exemple, BOLT peut réorganiser des blocs de base dans une fonction pour améliorer les performances du cache d'instruction, ou remplacer une séquence d'instructions du CVIC par un codage plus récent et plus court [BOLT : Outil d'optimisation et de mise en page binaires].

Conclusion : Le rôle évolutif du CDCI dans le développement des compilateurs

De la sélection des instructions et de la densité des codes à la fusion micro-op et à l'attribution des registres, la complexité du CDCI oblige les compilateurs à utiliser des analyses sophistiquées et des modèles de coûts. Malgré leur héritage, les processeurs modernes x86 ont adopté des techniques inspirées du CDCI comme les caches micro-op et la macrofusion, brouillant la ligne entre les deux paradigmes. Les compilateurs doivent s'adapter à chaque nouvelle micro-architecture, en conciliant l'utilisation de puissantes instructions du CDCI avec la nécessité de décoder l'efficacité et de conserver l'énergie.

En attendant, le CDCI restera probablement pertinent dans l'écosystème x86, tandis que le CDCI (conception RISC) gagne du terrain dans les serveurs et les ordinateurs portables. Ainsi, les rédacteurs de compilateurs doivent maintenir plusieurs cibles de backend, chacune avec son propre jeu de compromis. Pour les développeurs, comprendre comment la sortie du compilateur de forme du CDCI est la clé pour l'écriture de code qui peut être optimisée efficacement – par exemple, en utilisant des fonctions intrinsèques pour des instructions spécialisées ou en écrivant des boucles qui sont favorables à la macrofusion et à la cache μop.

Pour plus de détails, consultez les Intel® 64 et IA-32 Architectures Software Developer Manuals, qui détaillent chaque instruction x86 et son comportement, et les Manuels d'optimisation Agner Fog=" qui fournissent des tables de micro-architecture utilisées par les auteurs de compilateurs.