Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme
Dans ce chapitre, on va parler de l'endianess du processeur et de son alignement mémoire. Concrètement, on va s'intéresser à la façon dont le processeur repartit en mémoire les octets des données qu'il manipule. Ces deux paramètres sont sûrement déjà connus de ceux qui ont une expérience de la programmation assez conséquente. Les autres apprendront ce que c'est dans ce chapitre. Pour simplifier, ils sont à prendre en compte quand on échange des données entre registres et mémoire RAM.
La différence entre mots et bytes
[modifier | modifier le wikicode]Avant toute chose, nous allons reparler rapidement de la différence entre un byte et un mot. Les deux termes sont généralement polysémiques, avec plusieurs sens. Aussi, définir ce qu'est un mot est assez compliqué. Voyons les différents sens de ce terme, chacun étant utile dans un contexte particulier. La distinction entre un octet, un byte et un mot, se fait au niveau du processeur. Précisément, elle intervient au niveau des instructions d'accès mémoire, éventuellement de certaines opérations de traitement de données. Dans ce qui va suivre, nous allons faire la différence entre les architectures à mot, à byte, et à chaines de caractères.
Les mots et bytes : entiers et caractères
[modifier | modifier le wikicode]Vous savez sans doute qu'il existe des processeurs 8, 16, 32 ou 64 bits, ce nombre indiquant le nombre de bits utilisés pour coder un nombre entier. Et bien un mot est une donnée de taille fixe, qui a la même taille. En clair, un mot est toute donnée qui a la même taille qu'un entier : ça peut être un entier, une adresse mémoire, des données bit à bit, ou toute autre donnée. Précisons qu'il s'agit de la taille maximale, précision qui est importante car certains processeurs gèrent des entiers de taille distinctes. Par exemple, un processeur 32 bits peut avoir des instructions de calcul 16 ou 8 bit. Dans ce cas, c'est la taille maximale qui compte.
La taille des nombres entiers se répercute sur beaucoup d'aspects du jeu d'instruction, notamment la taille des registres. Un processeur 32 bits utilise des entiers codés sur 32 bits, et il a donc des registres entiers de 32 bits. Précisons qu'il s'agit là uniquement des registres entiers, les registres flottants peuvent être plus larges.
Un byte est une unité plus petite que le mot, typiquement un octet, initialement utilisée pour faciliter l'encodage du texte. Un byte correspondait dans le temps à un caractère de texte, codé en ASCII ou un autre codage similaire. La norme actuelle est de 8 bits par byte, mais ça n'a pas toujours été le cas. Elle s'est instaurée au début des années 1970, quand les processeurs ont commencés à s'adapter au traitement du texte. Avant, les processeurs utilisaient des bytes de 6, 7, 9 ou 10 bits.
Pour bien faire les choses, un mot contient un nombre entier de bytes, pas un bit de plus ou de moins. Le nombre d'octets dans un mot est généralement une puissance de deux pour simplifier les calculs. Cette règle souffre évidemment d'exceptions, qui posent quelques problèmes techniques en termes d’adressage, comme on le verra plus bas.
Sur les machines modernes, chaque byte a une adresse, ce qui fait que le processeur peut lire un byte indépendamment des autres. Le byte est souvent décrit comme la plus petite unité de mémoire que le processeur peut adresser. Mais quelques architectures très rares ne respectent pas cette règle, en permettant d'adresser carrément chaque bit de la mémoire indépendamment des autres.
Voyons rapidement la distinction entre mot et byte sur les processeurs 8, 16, 32 bits et plus. La distinction entre mot et byte est la plus simple sur les processeurs 16 bits : le mot fait 16 bits, le byte en fait 8, un mot est donc composé de deux bytes. Les processeurs 8 bits récents ne gèrent que des données de 8 bits, ce qui fait que le mot et le byte sont confondus. Le processeur manipule des mots qui sont de la même taille que le byte. Il a existé quelques processeurs capables de manipuler des données de 4 bits, notamment pour supporter le BCD, mais cela ne concernait que les instructions. Les registres et la mémoire utilisaient des bytes d'un octet. Sur les processeurs 32 bits, le byte fait un octet, le mot en fait 4, le processeur gère souvent des données de 16 bits intermédiaires.
Le lien avec le bus mémoire
[modifier | modifier le wikicode]Le terme "mot" vous a sans doute rappelé les chapitres sur les mémoires RAM/ROM, quand nous avons parlé des mots mémoire. Pour rappel, une mémoire RAM/ROM est découpée en mots mémoire, des groupes de bits qui sont transmis en une seule fois sur le bus mémoire. Par exemple, les mémoires modernes permettent de transmettre 64 bits en une seule fois sur le bus mémoire. La mémoire RAM est donc composée de groupes de 64 bits, chacun ayant une adresse mémoire, chaque groupe étant appelé une case mémoire ou encore un mot mémoire. Pour le dire autrement, il n'y a pas un octet par adresse, mais un mot mémoire dont la taille est de un ou plusieurs octets.
En théorie, les notions de mot et de mot mémoire sont distinctes. La notion de mot mémoire est une définition basée sur les transferts entre processeur et mémoire RAM/ROM, alors que la notion de mot processeur est liée au jeu d'instruction et aux registres. La distinction est importante car il est possible qu'un processeur utilise des mots plus grands que le bus mémoire. par exemple, un processeur 16 bits peut utiliser un bus mémoire 8 bits, bien que cela se fasse au détriment des performances (il faut charger les opérandes en deux fois, un octet après l'autre). Il faut donc distinguer le mot processeur, décrit dans la section précédente et contrasté au byte, du mot mémoire.
Il y a alors deux possibilités principales : le bus mémoire a la même taille qu'un byte, le bus a la même taille qu'un mot. En pratique, c'est la seconde solution qui est la plus utilisée, à savoir que le bus mémoire a la même taille qu'un mot processeur. Par exemple, les processeurs 16 bits gèrent des entiers codés sur 16 bits, ont des registres de 16 bits pour les entiers et adresses, et sont reliés à un bus mémoire de 16 bits. Le fait que le bus mémoire a la même taille qu'un mot a de nombreuses conséquences qu'on étudiera plus bas.
- Il a existé des systèmes où le bus mémoire avait une taille égale à la moitié d'un mot processeur, mais passons.
Sur les systèmes modernes, la présence de mémoires caches fait que le bus mémoire n'a pas de rapport direct avec la taille d'un mot. Le bus mémoire sert pour les transferts entre le cache et la RAM, pqui n'impliquent pas le processeur et les registres. Par contre, le taille du mot est importante pour les transferts entre cache et registres. Le bus qui relie le cache au processeur a typiquement une largeur égale à un mot mémoire.
L'adressage par mot et par byte
[modifier | modifier le wikicode]Pour résumer, un mot correspond à une donnée de même taille qu'un registre, ou du moins qu'un opérande/résultat entier. Un byte est une subdivision d'un mot, censé stocker un caractère, un mot contenant plusieurs bytes. La différence entre mot et byte est assez peu intuitive, mais elle va devenir claire avec ce qui suit. Nous allons aborder l'adressage par mot et l'adressage par byte. Le premier était utilisé sur d'anciens ordinateurs, dans les années 50 et 60, mais il permet d'introduire l'adressage par byte utilisé sur les ordinateurs modernes.
Les architectures à adressage par mot pures
[modifier | modifier le wikicode]Au tout début de l'informatique, avant les années 80, les ordinateurs ne géraient que des nombres entiers ou flottants, qui faisaient tous la même taille. La taille d'un mot, d'un nombre entier, était de 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. La taille la plus courante était 36 bits, suivie par 48 bits, le reste étant assez anecdotique. Pour donner quelques exemples, l'ordinateur ERA 1103 utilisait des opérandes/résultats de 36-bits, idem sur le PDP-10. Les processeurs avaient des registres de la taille d'un mot, à savoir 36/48 bits, et le bus mémoire faisait la même taille.
Un point très important est que les instructions de lecture/écriture lisaient/écrivaient des mots entiers, elles ne géraient pas de tailles autres. Par exemple, sur les processeurs 36 bits, les instructions mémoire lisaient ou écrivaient 36 bits, ni plus, ni moins. Pas de lecture/écriture sur 18 ou 12 bits, ni sur 8 ou 16 bits, par exemple. Le PDP-10, qui était un processeur 36 bits, ne gérait pas d'autre taille pour les données : c'était 36 bits pour tout le monde. De telles architectures n'utilisaient pas encore des octets, ni même de bytes, qui se sont démocratisés après. La raison à cela est qu'elles étaient conçues pour faire des calculs entiers/flottants, mais pas pour gérer du texte ou l'encodage BCD. N'utiliser que des mots suffisait, car un mot correspondait à un nombre entier, seuls opérandes acceptées par le processeur.
De plus, les ordinateurs de l'époque faisaient en sorte que la mémoire ait des cases mémoire de la même taille qu'un mot. Non seulement le bus mémoire avait la même taille, mais en plus, les adresses mémoire adressaient des mots entiers, pas des octets ni des bytes, ni quoique ce soit d'autre. Par exemple, le PDP-10 gérait des mots de 36 bits, ce qui fait que ses registres faisaient 36 bits, mais aussi qu'une adresse adressait un bloc de 36 bits. En clair, une adresse mémoire adressait un mot complet de 36 bits, pas un octet. De tels ordinateurs étaient appelés des architectures à adressage par mot. La mémoire était donc découpée en mots, chacun avait sa propre adresse, et le processeur échangeait des mots entre registres et mémoire RAM/ROM. Il n'y avait qu'une seule unité d'adressage, ce qui fait que les bytes n'existaient pas encore. La distinction entre byte et mot est apparue après, sur des ordinateurs/processeurs différents.
Par la suite, des processeurs ont permis d'adresser des données plus petites qu'un mot. L'intérêt était de faciliter la gestion du texte, qui est devenue importante quand les ordinateurs ont commencés à être utilisés en entreprise, puis dans les foyers. Les premiers mainframes étaient spécialisés dans le calcul scientifique ou dans les applications purement calculatoires, ils n'avaient pas, à gérer de texte, ce qui fait que l'adressage par mot était simple : il y avait une adresse par nombre entier, une case mémoire faisait la même taille qu'un registre, etc. Mais pour gérer du texte, c'est autre chose, car les caractères sont souvent encodés sur 6, 7, 8 bits. L'idée est alors de découper les mots en bytes de 6/7/8/9/10 bits, chacun correspondant à un caractère.
Les architectures à adressage par mot hybrides
[modifier | modifier le wikicode]Pour gérer des bytes, les architectures à adressage par mot se sont adaptées. L'idée est que le processeur ajoutait des instructions pour sélectionner un byte dans le mot. Une instruction d'accès mémoire devait alors préciser deux choses : l'adresse du mot à lire/écrire, et la position du byte dans le mot adressé. Par exemple, on pouvait demander à lire le mot à l'adresse 0x5F, et de récupérer uniquement le byte numéro 6. Il s'agit d'architectures adressables par mot car une adresse identifie un mot, pas un byte.
La sélection des bytes se faisait avec des instructions spécialisées : le processeur lisait des mots entiers, avant que le hardware du processeur sélectionne automatiquement le byte voulu. Un exemple est le PDP-6 et le PDP-10, qui avaient des instructions de lecture/écriture de ce type. Elles prenaient trois informations : l'adresse d'un mot, la position du byte dans le mot, et enfin la taille d'un byte ! L'adressage était donc très flexible, car on pouvait configurer la taille du byte. Outre l'instruction de lecture LDB et celle d'écriture DPB, d'autres instructions permettaient de manipuler des bytes. L'instruction IBP incrémentait le numéro du pseudo-byte, par exemple.
Mais de telles instructions avaient un problème : il fallait calculer l'adresse du mot mémoire et la position du byte dans le mot. Rien de compliqué en soit, mais cela demande une division et une opération modulo, ce qui n'est pas très pratique. En général, le texte est stocké dans un tableau de caractères, qui est parcouru du premier caractère vers le dernier. Gérer du texte demandait donc de gérer adresse et position : on devait incrémenter la position pour passer au caractère suivant, puis incrémenter l'adresse tous les 2/3/4/5/6 caractères. Pour éviter cela, les processeurs ont inventé l'adressage par byte.
Les architectures à adressage par byte
[modifier | modifier le wikicode]L'idée derrière l'adressage par byte est très simple : pour le processeur, chaque byte a sa propre adresse. La mémoire est vue par le processeur comme un regroupement de bytes, chacun numérotés avec une adresse. Concrètement, sur les processeurs modernes, chaque octet de la mémoire a sa propre adresse, peu importe la taille du mot processeur. Pour résumer, la distinction entre byte et mot est la suivante : le byte est utilisé pour adresser des caractères ou des octets, le mot pour adresser des entiers/flottants. Souvent, le byte est la plus petite donnée adressable et le mot est la plus grande, mais ce n'est pas systématique, quelques architectures ne respectent pas cette règle.
Les processeurs à adressage par byte ont souvent plusieurs instructions de lecture/écriture, chacune pour une taille précise. Il y a au minimum une instruction de lecture qui lit un byte en mémoire, une autre qui lit un mot complet, éventuellement des instructions pour les tailles intermédiaires. Par exemple, un processeur 64 bit a des instructions pour lire 8 bits, une autre pour lire 16 bits, une autre pour en lire 32, et enfin une pour lire 64 bits. Idem pour les instructions d'écriture, et les autres instructions d'accès mémoire. Dans ce cas, le byte vaut 8 bits, le mot en fait 64, les tailles intermédiaires n'ont pas de nom.
Une autre possibilité est celle d'une instruction de lecture unique, qu'on peut configurer pour lire/écrire un octet, deux, quatre, huit. Reprenons l'exemple de l'instruction LOAD qui lit une donnée en mémoire. Pour lire un byte isolé, il suffit de préciser l'adresse du byte, et qu'il faut lire un seul byte. Pour lire 16 ou 32 bits, l'instruction LOAD prend l'adresse du premier byte des 16/32 bits, et lit les 16/32 bits à partir de ce byte. En clair, seuls les bytes ont une adresse du point de vue du processeur. Je dis du point de vue du processeur, car nous verrons que c'est plus compliqué pour ce qui est de la mémoire RAM/ROM.
Ainsi, le processeur peut adresser un caractère directement, sans devoir calculer une adresse et une position : l'adresse du caractère suffit. L'avantage de l'adressage par byte est que l'on peut plus facilement modifier des données de petite taille. Par exemple, imaginons qu'un programmeur manipule du texte, avec des caractères codés sur un octet. S'il veut remplacer les lettres majuscules par des minuscules, il doit changer chaque lettre l'une après l'autre. Avec un adressage par mot, il doit lire un mot entier, modifier chaque octet en utilisant des opérations de masquage, puis écrire le mot final. Avec un adressage par byte, il peut lire chaque byte indépendamment, le modifier sans recourir à des opérations de masquage, puis écrire le résultat.
Un désavantage de l'adressage par byte est que l'on adresse moins de mémoire pour un nombre d'adresses égal. Si on a un processeur qui gère des adresses de 16 bits, on peut adresser 2^16 = 65 536 adresses. Avec l'adressage par byte, on ne pourra adresser que 65 536 bytes/octets, peu importe la taille du mot mémoire. Avec l'adressage par mot, on pourra adresser 65 536 mots de plusieurs octets chacun. Par exemple, avec un mot mémoire de 4 octets, on pourra adresser 65 536 × 4 octets. L'adressage par mot permet donc d'adresser plus de mémoire avec les mêmes adresses. C'est d'ailleurs pour cette raison que certains processeurs 16 bits spécialisés dans le traitement de signal (des DSP 16 bits) utilisent l'adressage par mot : pour adresser plus de mémoire avec des adresses codées sur 16 bits.

Les architectures à adressage par byte ont d'autres défauts. Le fait qu'un mot contienne plusieurs octets/bytes a de nombreuses conséquences, desquelles naissent les contraintes d'alignement, de boutisme et autres. Dans ce qui suit, nous allons étudier les défauts des architectures adressables par byte, et allons laisser de côté les architectures adressables par mot. La raison est que toutes les architectures modernes sont adressables par byte, les seules architectures adressables par mot étant de très vieux ordinateurs aujourd'hui disparus.
Les architectures à mot de taille variable
[modifier | modifier le wikicode]D'anciens ordinateurs codaient leurs nombres sur un nombre variable de bytes, ce qui leur a valu le nom d'architectures à mots de taille variable. La grande majorité étaient des architectures décimales, à savoir des ordinateurs qui utilisaient des nombres encodés en BCD ou dans un encodage similaire. De tels processeurs encodaient des nombres sous la forme d'une suite de chiffres décimaux codés en BCD sur 4 bits, voire de 5/6 bits pour les ordinateurs qui ajoutaient un bit de parité/ECC par chiffre décimal. Les calculs se faisaient chiffre par chiffre, au rythme d'un chiffre utilisé comme opérande par cycle d'horloge. Le processeur passait automatiquement d'un chiffre au suivant pour chaque opérande.
Suivant l'ordinateur, la suite de chiffres était sois de même taille pour tous les nombres, soit avait une taille variable avec une taille maximale, soit n'avait tout simplement pas de taille maximale. Ce sont ces deux derniers cas qui intéressent ici.
Le premières architectures mot variable encodaient un entier BCD avec : une suite de chiffres BCD, précédée par un ou deux bytes pour encoder la longueur de la suite de chiffres. Les opérandes avaient ainsi une taille maximale, qui dépendait du codage de la longueur, du nombre de bits utilisés l'encoder. Par exemple, si on utilisait 8 bits pour encoder la longueur, les opérandes allaient de 0 à 255 chiffres BCD. Mais la plupart des ordinateurs ne gérait pas des tailles aussi importantes. Par exemple, de nombreux processeurs IBM géraient des opérandes allant jusqu'à 10 chiffres BCD maximum, pas plus.
D'autres architectures codaient les nombres par des chaines de caractères terminées par un byte de terminaison. La chaine de caractère contenait uniquement des chiffres, codés en BCD. Les bytes stockaient chacun un caractère, qui était utilisé pour encoder soit un chiffre décimal, soit un byte de terminaison. La taille d'un caractère était généralement de 5/6 bits, vu qu'il fallait au minimum coder les chiffres BCD et des symboles supplémentaires. Un exemple est celui des IBM 1400 series, qui utilisaient des chaines de caractères séparées par deux bytes : un byte de wordmark au début, et un byte de record mark à la fin. Les caractères étaient codés sur 6 bits. Chaque caractère/chiffre avait sa propre adresse, ce qui fait que l'architecture est techniquement adressable par byte, alors que les mots correspondaient aux nombres de taille variable.
L'adressage à la granularité d'un bit
[modifier | modifier le wikicode]Nous venons de voir que l'adressage par byte est utile pour modifier des caractères sans avoir à modifier un mot complet. Sans adressage par byte, modifier un caractère demande de lire un mot entier, de modifier le caractère, puis d'enregistrer le mot final. Maintenant, rappelons-nous les chapitres sur les opérations logiques. Nous avions vu qu'il est fréquent de faire la même chose, mais avec des bits : modifier certains bits d'un nombre, sans modifier les autres. Il arrive occasionnellement que les programmeurs utilisent des bitfields, à savoir qu'ils mémorisent plusieurs informations dans un seul entier. Un exemple classique est le stockage des dates : on stocke le jour, le mois et l'année dans un seul entier, dont quelques bits encodent la journée, d'autres le mois, d'autres l'année. De telle situations demandent de modifier certains bits d'un bitfield, mais pas les autres.
Un autre exemple est celui de la configuration des périphériques. Nous en avions déjà parlé rapidement dans le chapitre "l'architecture de base", mais configurer des périphériques demande de modifier la valeur de registres de configuration, qui contiennent justement des bitfields. Par exemple, pour configurer une carte graphique, il y a un registre pour configurer la résolution et la fréquence d'affichage, ces trois nombres étant concaténés et stockés dans un registre. De plus, la plupart des registres de configuration disposent de bits qui permettent d'activer ou de désactiver des fonctionnalités. Activer ou désactiver une fonctionnalité demande alors de modifier un seul bit dans le registre de configuration adéquat.
Les registres de configuration des périphérique sont adressés avec une adresse mémoire. Et cela peu importe qu'ils soit mappés en mémoire RAM, ou dans un espace d'adressage séparés : il y a toujours une adresse mémoire bien précise qui permet d'adresser un registre. Vous l'avez sans doute vu venir, mais modifier un seul bit demande de charger un mot/byte complet, de modifier le bit dans les registres, puis d'enregistrer le byte/mot final. Encore une fois, on doit effectuer un accès en lecture-modification-écriture (Read-Modify-Write). Et cela pose de nombreux problèmes techniques dont on ne pas vraiment parler pour le moment, notamment des problèmes d'atomicité sur les architectures multicœurs. Et au-delà de ces problèmes techniques, ce n'est pas pratique pour les programmeurs ou les compilateurs.
Heureusement, à problème identique, solution identique. Une première solution a été d'ajouter des instructions processeurs capables d'effectuer l'accès en lecture-modification-écriture automatiquement. Les instructions en question sont des instructions Clear Bit, Set Bit, Invert Bit, et quelques autres. Elles ne se contentent pas de faire une opération logique, mais font aussi la lecture et l'écriture du résultat en mémoire RAM. L'instruction prend comme opérandes deux informations : l'adresse de l'octet qui contient le bit à modifier, la position de ce bit dans l'octet. Il faut évidemment les calculer, rien de compliqué, dans le programmeur doit faire cela à la main. Il s'agit d'une solution similaire à celle des architecture à adressage par mot hybrides, avec des instructions pour gérer des bytes.
Et enfin, une autre solution utilise l'adressage à la granularité d'un bit, que nous raccourcirons en adressage par bit. Ce terme compliqué cache un concept tout simple : il n'y a pas une adresse par byte, ni une adresse par mot processeur/mémoire, mais une adresse pour chaque bit de la mémoire ! S'il a existé des processeurs et des mémoires bit-adressables, il faut avouer qu'ils sont très rares. De tels processeurs gardent la capacité d'adresser un byte ou un mot. L'adresse d'un byte/mot étant l'adresse de son premier bit, de la même manière que l'adresse d'un mot est l'adresse de son premier byte avec l'adressage par byte. Sur ces architectures, le byte n'est pas la plus petite unité mémoire adressable.
Il est possible d'avoir un adressage par bit au niveau du processeur, alors que les registres de configurations ne permettent pas d'adresser un bit distinct. Dans ce cas, c'est soit le périphérique, soit le processeur, qui se chargent d'effectuer un accès en lecture-modification-écriture en sous-main, de manière automatique, sans que le programmeur ait quoique ce soit à faire.
Une des rares utilisation actuelle de l'adressage par bit est la technique dite de bit-banding. Elle est présente sur certains processeurs ARM, potentiellement d'autres. L'idée est que l'on peut manipuler les registres de configuration de deux manières : soit avec l'adressage par byte/mot, soit avec l'adressage par bit. Un registre de configuration a alors plusieurs adresses : une adresse globale pour le modifier entièrement en écrivant un byte/mot dedans, une adresse pour chacun de ses bit.
Les deux adresses peuvent en théorie être dans deux espaces d'adressage séparés, mais ce n'est pas la solution retenue sur les processeurs ARM, qui mettent ces adresses dans un seul espace d'adressage. Elle ne concerne qu'une petite partie de la mémoire RAM de l'ordinateur, à savoir deux blocs de 1 mébi-octet sur l'ARM Cortex M3. Le premier bloc est mappé dans l'intervalle allant des adresses 0x20000000h à 0x20100000h, ses bits sont adressés dans l'intervalle allant des adresses 0x22000000h à 0x23FFFFFFh. Le seconde intervalle est plus grand, car le premier utiliser une adresse par mot, le second a une adresse par bit.
L'implémentation matérielle de l'adressage par byte
[modifier | modifier le wikicode]Implémenter l'adressage par byte demande de faire quelques modifications au niveau du processeur, mais aussi parfois du bus mémoire et de la mémoire RAM. Avant de poursuivre, rappelons que la notion de byte est avant tout liée au jeu d'instruction, mais qu'elle ne dit rien du bus mémoire ! Il est parfaitement possible d'utiliser un bus mémoire d'une taille différente de celle du byte ou du mot. La largeur du bus mémoire, la taille d'un mot, et la taille d'un byte, ne sont pas forcément corrélées. Néanmoins, deux implémentations principales sont possibles.
La première utilise une mémoire dont chaque case mémoire est un byte. Le bus mémoire a la même taille qu'un byte, ce qui fait que les lectures/écritures se font byte par byte, ce qui est très lent. Une autre solution utilise un bus mémoire de la même taille qu'un mot. Dans ce cas, le processeur lit/écrit des mots mémoire entiers, mais il peut sélectionner les bytes qui l'intéressent si besoin. Il peut donc lire/écrire un mot entier en une seule fois s'il manipule des entiers, mais ne garder que les bytes adéquats quand il travaille sur du texte.
Les architectures avec une mémoire adressable par byte
[modifier | modifier le wikicode]La première implémentation a un bus mémoire de la taille d'un byte, qui transmet un byte à la fois. En clair, la largeur du bus mémoire est celle du byte. Le moindre accès mémoire se fait byte par byte, donc en plusieurs cycles d'horloge. Par exemple, sur un processeur 64 bits, la lecture d'un mot complet se fera octet par octet, ce qui demandera 8 cycles d'horloge, cycles d'horloge mémoire qui plus est. Par contre, lire ou écrire un byte se fera en un seul cycle d'horloge mémoire, ce qui n'est pas le cas avec l'autre implémentation. Cette implémentation a donc un désavantage en termes de performance quand on manipule des mots, mais un avantage quand on manipule des bytes. La performance dépend de plus de la taille des données lue/écrites. On prend moins de temps à lire une donnée courte qu'une donnée longue.
Un autre avantage est qu'on peut lire ou écrire un mot, peu importe son adresse. Pour donner un exemple, je peux parfaitement lire une donnée de 16 bits à l'adresse 4, puis lire une autre donnée de 16 bits à l'adresse 5 sans aucun problème. En conséquence, il n'y a pas de contraintes d'alignements et les problèmes que nous allons aborder dans la suite n'existent pas.

Les architectures avec une mémoire adressable par mot
[modifier | modifier le wikicode]Avec la seconde implémentation, le bus mémoire a la largeur nécessaire pour lire un mot entier. Il y a alors confusion entre un mot au sens du jeu d'instruction, et un mot mémoire. Pour rappel, une donnée qui a la même taille que le bus de données est appelée un mot mémoire.
Le processeur peut lire/écrire un mot mémoire entier dans ses registres, en un seul accès mémoire, en un cycle d'horloge mémoire. Il y a donc un avantage en termes de performance quand on manipule des mots. Pour la lecture/écriture de données plus petites qu'un mot, tout dépend de si on fait une lecture ou une écriture. Lire un byte prend le même temps : il suffit de lire un mot entier et de ne sélectionner que le byte voulu. Et il en est de même pour les tailles intermédiaires entre mot et byte : le processeur charge un mot complet, puis sélectionne les octets adéquats, grâce un multiplexeur.
La gestion des écritures est par contre désavantagée. Le problème est que les écritures se font mot par mot, pas byte par byte. Ne modifier qu'un seul byte demande de lire le mot qui contient le byte à modifier, modifier le byte, et enfin écrire le résultat. Et le même problème a lieu pour les tailles intermédiaires. Par exemple, pour un processeur 32 bits, écrire 16 bits demande de lire un mot de 32 bits, ne modifier que les 126 bits adéquats dedans, et écrire le résultat dans le mot mémoire final. Le tout demande des circuits de masquage et de décalage assez complexe.

Un problème avec cette implémentation est que l'adressage de la mémoire et du CPU ne sont pas compatibles : le processeur utilise une adresse par byte, la mémoire une adresse par mot mémoire ! Il faut donc distinguer les adresses gérées par la mémoire et celles gérées par le processeur. Le processeur génère des adresses d'octet, qui permettent de sélectionner un octet bien précis (remplacez octet par byte pour plus de généralité). L'octet en question est dans une case mémoire bien précise, qui a elle-même une adresse mémoire. Lors d'un accès mémoire, l'adresse d'octet est convertie en une adresse mémoire, la case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire, nous allons détailler comment ils fonctionnent dans ce qui suit.

Par convention, l'adresse d'un mot est l'adresse de son octet de poids faible. Les autres octets du mot ne sont pas adressables par la mémoire. Par exemple, si on prend un mot de 8 octets, on est certain qu'une adresse sur 8 disparaîtra. L'adresse du mot est utilisée pour communiquer avec la mémoire, mais cela ne signifie pas que l'adresse des octets est inutile au-delà du calcul de l'adresse du mot. En effet, l'accès à un octet précis demande de déterminer la position de l'octet dans le mot à partir de l'adresse de l’octet.
Prenons un processeur ayant des mots de 4 octets et répertorions les adresses utilisables. Le premier mot contient les octets d'adresse 0, 1, 2 et 3. L'adresse zéro est l'adresse de l'octet de poids faible et sert donc d'adresse au premier mot, les autres sont inutilisables sur le bus mémoire. Le second mot contient les adresses 4, 5, 6 et 7, l'adresse 4 est l'adresse du mot, les autres sont inutilisables. Et ainsi de suite. Si on fait une liste exhaustive des adresses valides et invalides, on remarque que seules les adresses multiples de 4 sont utilisables. Et ceux qui sont encore plus observateurs remarqueront que 4 est la taille d'un mot.
Dans l'exemple précédent, les adresses utilisables sont multiples de la taille d'un mot. Sachez que cela fonctionne quelle que soit la taille du mot. Si N est la taille d'un mot, alors seules les adresses multiples de N seront utilisables. Avec ce résultat, on peut trouver une procédure qui nous donne l'adresse d'un mot à partir de l'adresse d'un octet. Si un mot contient N bytes, alors l'adresse du mot se calcule en divisant l'adresse du byte par N. La position du byte dans le mot est quant à elle le reste de cette division. Un reste de 0 nous dit que l'octet est le premier du mot, un reste de 1 nous dit qu'il est le second, etc. Et c'est cette position qui est utilisée pour configurer les multiplexeurs, quand on accède à un byte isolé.

Le processeur peut donc adresser la mémoire RAM en traduisant les adresses des octets en adresses de mot. Il lui suffit de faire une division pour cela. Il conserve aussi le reste de la division dans un registre pour sélectionner l'octet une fois la lecture terminée. Un accès mémoire se fait donc comme suit : il reçoit l'adresse à lire, il calcule l'adresse du mot, effectue la lecture, reçoit le mot à lire, et utilise le reste pour sélectionner l'octet final si besoin. La dernière étape est facultative et n'est présente que si on lit une donnée plus petite qu'un mot.
La division est une opération assez complexe, mais il y a moyen de ruser. L'idée est de faire en sorte que N soit une puissance de deux. La division se traduit alors par un vulgaire décalage vers la droite, le calcul du reste par une simple opération de masquage. C'est la raison pour laquelle les processeurs actuels utilisent des mots de 1, 2, 4, 8 octets. Sans cela, les accès mémoire seraient bien plus lents. De plus, cela permet d'économiser des fils sur le bus d'adresse. Si la taille d'un mot est égale à , seules les adresses multiples de seront utilisables. Or, ces adresses se reconnaissent facilement : leurs n bits de poids faibles valent zéro. On n'a donc pas besoin de câbler les fils correspondant à ces bits de poids faible.
L'alignement mémoire
[modifier | modifier le wikicode]Dans la section précédente, nous avons évoqué le cas où un processeur à adressage par byte est couplé à une mémoire adressable par mot. Sur de telles architectures, des problèmes surviennent quand les lectures/écritures se font par mots entiers. Le processeur fournit l'adresse d'un byte, mais lit un mot entier à partir de ce byte. Par exemple, prenons une lecture d'un mot complet : celle-ci précise l'adresse d'un byte. Sur un CPU 64 bits, le processeur lit alors 64 bits d'un coup à partir de l'adresse du byte. Et cela peut poser quelques problèmes, dont la résolution demande de respecter des restrictions sur la place de chaque mot en mémoire, restrictions résumées sous le nom d'alignement mémoire.
L'exemple sur les processeurs 16 bits
[modifier | modifier le wikicode]Pour faire comprendre ce qu'est l'alignement mémoire, nous allons prendre l'exemple d'un processeur 16 bits connecté à une mémoire de 16 bits, via un bus mémoire de 16 bits. Le processeur utilisant l'adressage par byte, chaque octet de la mémoire a sa propre adresse. Par contre, le processeur lit et écrit des paquets de 16 bits, soit deux octets.
- Pour rappel, un groupe de deux octets est appelé un doublet.
Pour la mémoire, les octets sont regroupés en groupes de deux, en doublets mémoire. Pour la mémoire, un doublet mémoire a une adresse unique. Et cela ne colle pas avec l'adressage par byte utilisé par le processeur, il y a une différence entre les adresses du processeur et celles de la mémoire. Il y a une adresse par doublet pour la mémoire, une adresse par octet pour le processeur. L'adresse mémoire a donc un bit de moins que l'adresse processeur. Pour faire la distinction, nous utiliserons les termes : adresse mémoire et adresse processeur.
Lorsque le processeur lit un doublet, il lit le premier octet à une adresse processeur et l'octet suivant dans l'adresse processeur suivante. Les adresses processeur sont donc regroupées par groupes de deux : l'adresse 0 et 1 adressent toutes deux le premier doublet, l'adresse 2 et 3 adressent le second doublet, etc. Un doublet est donc identifié par deux adresses processeur : une adresse paire et une adresse impaire. Nous allons partir du principe que l'octet de poids faible est dans l'adresse paire, l'octet de poids fort dans l'adresse impaire. Les règles de boutisme autorise de faire l'inverse, mais ce n'est pas le choix le plus intuitif.
L'alignement mémoire dit quoi faire lorsque le processeur veut lire/écrire 16 bits à une adresse impaire. S'il veut lire 16 bits à une adresse impaire, les deux octets seront dans des doublets mémoire différents. Le premier sera l'octet de poids fort d'un doublet, l'autre sera l'octet de poids faible du doublet suivant. Le doublet que le processeur veut lire/écrire est à cheval sur deux doublets mémoire. Et le processeur ne gère pas cette situation naturellement.

Pour résoudre ce problème, il y a deux solutions : imposer l'alignement mémoire, supporter les accès non-alignés.
L'alignement mémoire strict n'autorise que les accès mémoire à des adresses mémoires paires. Tout accès à une adresse mémoire impaire lève une exception matérielle, signe que c'est une erreur matérielle. Par contre, les adresses paires sont autorisées car une adresse paire identifie un doublet mémoire, que le processeur peut lire/écrire en une seule fois, à travers le bus mémoire. Le processeur gère donc uniquement des lectures/écritures de doublet, au sens de doublet mémoire.
Sans alignement mémoire, on peut lire/écrire 16 bits à partir d'une adresse paire comme impaire, les deux sont autorisées. Pour cela, le processeur doit gérer les lectures/écritures de 16 bits à des adresses impaires. Vu que les 16 bits demandés sont à cheval sur deux doublets mémoire, le processeur doit lire les deux doublets mémoire, sélectionner les octets adéquats, et les concaténer pour obtenir les 16 bits finaux.
Pour ce qui est de lire/écrire des octets, l'alignement mémoire ne pose aucune contrainte, vu qu'un octet n'est jamais à cheval sur un doublet,quadruplet ou autre. Le processeur peut demander à lire ou écrire un seul octet, mais la mémoire fournira deux octets et le processeur devra n'en conserver qu'un. Donc, il faut prévoir un système pour sélectionner l'octet demandé dans un doublet.
Pour la lecture d'un octet à une adresse paire, le processeur lit un doublet et masque l'octet de poids fort, chargé en trop. Pour une lecture d'un octet à une adresse impaire, le processeur lit un doublet, déplace l'octet de poids fort dans l'octet de poids faible, et masque l'octet inutile. Dans tous les cas, le processeur ne lit/écrit qu'un doublet à la fois et sélectionne les octets demandés. Les écritures sont plus complexes, car elles demandent de lire un doublet, de modifier l'octet adéquat, et de réécrire le doublet final en RAM.
Pour les lectures, le tout est réalisé par un circuit intégré au processeur, qui est directement relié au bus mémoire. Il prend en entrée deux bits : le premier indique s'il faut faire une lecture sur 8 ou 16 bits, le second qui indique s'il faut lire l'octet de poids faible ou fort. Le circuit est alors composé d'un circuit de masquage et d'un multiplexeur. Le multiplexeur est utilisé pour choisir l'octet de poids faible ou fort. Le circuit de masquage met à zéro l'octet de poids fort en cas d'accès sur 8 bits.

Une autre possibilité est celle utilisée sur les anciens processeurs Intel 16 bits, qui utilisaient deux bits. Le premier est le bit de poids fort de l'adresse processeur, qui indiquait si l'adresse est paire ou non. Le second bit, nommé BHE, indique s'il fallait masquer l'octet de poids fort ou non.
| Adresse paire | Adresse impaire | |
|---|---|---|
| BHE = 0 | Lecture 16 bits | Lecture octet de poids fort |
| BHE = 1 | Lecture octet de poids faible | Pas de lecture |
L'alignement mémoire des données
[modifier | modifier le wikicode]L'exemple précédent nous a montré ce qu'il en était sur les processeurs 16 bits. Mais pour généraliser le concept, nous allons voir le cas des processeurs 32 bits et plus. Nous venons de voir que l'alignement mémoire sur 16 bits impose des contraintes quant à l'adressage de la mémoire, en empêchant de lire des données qui sont à cheval sur deux doublets. Sur 32 bits, c'est la même chose, mais avec des quadruplets (des groupes de 4 octets, soit 32 bits) : impossible de lire des données si elles sont à cheval sur deux quadruplets mémoire. Sur 64 bits, c'est la même chose, mais avec des octuplets de 8 octets/ 64 bits : impossible de lire des données si elles sont à cheval sur deux octuplets mémoire.
La différence, c'est que la situation peut se présenter même si on ne lit pas 32/64 bits. Imaginons le cas particulier suivant : je dispose d'un processeur utilisant des mots de 4 octets. Je dispose aussi d'un programme qui doit manipuler un caractère stocké sur 1 octet, un entier de 4 octets et une donnée de deux octets. Mais un problème se pose : le programme qui manipule ces données a été programmé par quelqu'un qui n'était pas au courant de ces histoire d'alignement, et il a répartit mes données un peu n'importe comment. Supposons que cet entier soit stocké à une adresse non-multiple de 4. Par exemple :
| Adresse | Octet 4 | Octet 3 | Octet 2 | Octet 1 |
|---|---|---|---|---|
| 0x 0000 0000 | Caractère | Entier | Entier | Entier |
| 0x 0000 0004 | Entier | Donnée | Donnée | |
| 0x 0000 0008 |
La lecture ou écriture du caractère ne pose pas de problème, vu qu'il ne fait qu'un seul byte. Pour la donnée de 2 octets, c'est la même chose, car elle tient toute entière dans un mot mémoire. La lire demande de lire le mot et de masquer les octets inutiles. Mais pour l'entier, ça ne marche pas car il est à cheval sur deux mots ! On dit que l'entier n'est pas aligné en mémoire. En conséquence, impossible de le charger en une seule fois
Pour résumer, avec un bus mémoire de 32 bits, le problème peut survenir si le processeur veut lire 32 bits, mais aussi 16 bits. Si on veut lire 16 bits, mais que le premier octet est dans un quadruplet, et l'autre octet dans un autre quadruplet, l'alignement mémoire intervient. Idem si on veut lire 4 octets, mais que les 3 premiers sont dans un quadruplet, pas le dernier. Tout cela est plus facile à comprendre avec un exemple.
La situation est gérée différemment suivant le processeur. Sur certains processeurs, la donnée est chargée en deux fois : c'est légèrement plus lent que la charger en une seule fois, mais ça passe. On dit que le processeur gère des accès mémoire non-alignés. D'autres processeurs ne gérent pas ce genre d'accès mémoire et les traitent comme une erreur, similaire à une division par zéro, et lève une exception matérielle. Si on est chanceux, la routine d'exception charge la donnée en deux fois. Mais sur d'autres processeurs, le programme responsable de cet accès mémoire en dehors des clous se fait sauvagement planter. Par exemple, essayez de manipuler une donnée qui n'est pas "alignée" dans un mot de 16 octets avec une instruction SSE, vous aurez droit à un joli petit crash !
Pour éviter ce genre de choses, les compilateurs utilisés pour des langages de haut niveau préfèrent rajouter des données inutiles (on dit aussi du bourrage) de façon à ce que chaque donnée soit bien alignée sur le bon nombre d'octets. En reprenant notre exemple du dessus, et en notant le bourrage X, on obtiendrait ceci :
| Adresse | Octet 4 | Octet 3 | Octet 2 | Octet 1 |
|---|---|---|---|---|
| 0x 0000 0000 | Caractère | X | X | X |
| 0x 0000 0004 | Entier | Entier | Entier | Entier |
| 0x 0000 0008 | Donnée | Donnée | X | X |
Comme vous le voyez, de la mémoire est gâchée inutilement. Et quand on sait que de la mémoire cache est gâchée ainsi, ça peut jouer un peu sur les performances. Il y a cependant des situations dans lesquelles rajouter du bourrage est une bonne chose et permet des gains en performances assez abominables (une sombre histoire de cache dans les architectures multiprocesseurs ou multi-cœurs, mais je n'en dit pas plus).
L'alignement mémoire se gère dans certains langages (comme le C, le C++ ou l'ADA), en gérant l'ordre de déclaration des variables. Essayez toujours de déclarer vos variables de façon à remplir un mot intégralement ou le plus possible. Renseignez-vous sur le bourrage, et essayez de savoir quelle est la taille des données en regardant la norme de vos langages.
L'alignement des instructions en mémoire
[modifier | modifier le wikicode]Le processeur peut incorporer des contraintes sur l'alignement des instructions, au même titre que les contraintes d'alignement sur les données vues précédemment. Concrètement, il vaut mieux que les instructions rentrent dans un mot mémoire. La raison est que si on veut lire une instruction en un seul cycle, il faut qu'une instruction rentre toute entière dans le bus mémoire, donc dans un mot mémoire. Les processeurs RISC se débrouillent donc pour que les instructions rentrent dans un mot mémoire, donc dans un mot processeur.
Les instructions sont donc alignées sur un mot mémoire, elles doivent respecter les mêmes contraintes d'alignement que les données. Elles peuvent être plus courtes ou plus longues qu'un mot, mais elles doivent commencer à la première adresse d'un mot mémoire. La conséquence est que les instructions sont placées à des adresses précises. Par exemple, prenons un ordinateur avec des mots mémoire de 8 octets. La première instruction prend les 8 premiers octets de la mémoire, la seconde prend les 8 octets suivants, etc. En faisant cela, l'adresse d'une instruction est toujours un multiple de 8. Et on peut généraliser pour toute instruction de taille fixe : si elle fait X octets, son adresse est un multiple de X.
Généralement, on prend X une puissance de deux pour simplifier beaucoup de choses. Notamment, cela permet de simplifier le program counter : quelques bits de poids faible deviennent inutiles. Par exemple, si on prend des instructions de 4 octets, les adresses des instructions sont des multiples de 4, donc les deux bits de poids faible de l'adresse sont toujours 00 et ne sont pas intégrés dans le program counter. Le program counter est alors plus court de deux bits. Idem avec des instructions de 8 octets qui font économiser 3 bits, ou avec des instructions de 16 octets qui font économiser 4 bits.
Les processeurs CISC, avec leurs instructions de longueur variable potentiellement lues en plusieurs fois, ne sont pas concernés. Les instructions de taille variable ne sont généralement pas alignées. Sur certains processeurs, les instructions n'ont pas de contraintes d'alignement du tout. Leur chargement est donc plus compliqué et demande des méthodes précises qui seront vues dans le chapitre sur l'unité de chargement du processeur. Évidemment, le chargement d'instructions non-alignées est donc plus lent. En conséquence, même si le processeur supporte des instructions non-alignées, les compilateurs ont tendance à aligner les instructions comme les données, sur la taille d'un mot mémoire, afin de gagner en performance. D'autres architectures CISC ont des contraintes d'alignement bizarres, pour des raisons historiques. Par exemple, les premiers processeurs x86 16 bits imposaient des instructions alignées sur 16 bits et cette contrainte est restée sur les processeurs 32 bits.
Que ce soit pour des instructions de taille fixe ou variables, les circuits de chargement des instructions et les circuits d'accès mémoire ne sont pas les mêmes, ce qui fait que leurs contraintes d'alignement peuvent être différentes. On peut avoir quatre possibilités : des instructions non-alignées et des données alignées, l'inverse, les deux qui sont alignées, les deux qui ne sont pas alignées. Par exemple, il se peut qu'un processeur accepte des données non-alignées, mais ne gère pas des instructions non-alignées ! Le cas le plus simple, fréquent sur les architectures RISC, est d'avoir des instructions et données alignées de la même manière.
De plus, sur les processeurs où les deux sont alignés, on peut avoir un alignement différent pour les données et les instructions. Par exemple, pour un processeur qui utilise des instructions de 8 octets, mais des données de 4 octets. Les différences d'alignements posent une contrainte sur l'économie des bits sur le bus d'adresse. Il faut alors regarder ce qui se passe sur l'alignement des données. Par exemple, pour un processeur qui utilise des instructions de 8 octets, mais des données de 4 octets, on ne pourra économiser que deux bits, pour respecter l'alignement des données. Ou encore, sur un processeur avec des instructions alignées sur 8 octets, mais des données non-alignées, on ne pourra rien économiser.
Les architectures CISC utilisent souvent des contraintes d'alignement, avec généralement des instructions de taille variables non-alignées, mais des données alignées. Les deux dernières possibilités ne sont presque jamais utilisées. Un cas particulier est celui de l'Intel iAPX 432, dont les instructions étaient non-alignées au niveau des bits ! Leur taille variable faisait que la taille des instructions n'était pas un multiple d'octets. Il était possible d'avoir des instructions larges de 23 bits, d'autres de 41 bits, ou toute autre valeur non-divisible par 8. Un octet pouvait contenir des morceaux de deux instructions, à cheval sur l'octet. Ce comportement fort peu pratique faisait que l'implémentation de l'unité d"e chargement était complexe.
Le boutisme : une spécificité de l'adressage par byte
[modifier | modifier le wikicode]Un autre problème lié à l'adressage par byte est lié au fait que l'on a plusieurs bytes par mot : dans quel ordre placer les bytes dans un mot ? On peut introduire le tout par une analogie avec les langues humaines : certaines s’écrivent de gauche à droite et d'autres de droite à gauche. Dans un ordinateur, c'est pareil avec les bytes/octets des mots mémoire : on peut les écrire soit de gauche à droite, soit de droite à gauche. Quand on veut parler de cet ordre d'écriture, on parle de boutisme (endianness).
Dans ce qui suit, nous allons partir du principe que le byte fait un octet, mais gardez dans un coin de votre tête que ce n'a pas toujours été le cas. Les explications qui vont suivre restent valide peu importe la taille du byte.
Les différents types de boutisme
[modifier | modifier le wikicode]Les deux types de boutisme les plus simples sont le gros-boutisme et le petit-boutisme. Sur les processeurs gros-boutistes, la donnée est stockée des adresses les plus faibles vers les adresses plus grande. Pour rendre cela plus clair, prenons un entier qui prend plusieurs octets et qui est stocké entre deux adresses. L'octet de poids fort de l'entier est stocké dans l'adresse la plus faible, et inversement pour le poids faible qui est stocké dans l'adresse la plus grande. Sur les processeurs petit-boutistes, c'est l'inverse : l'octet de poids faible de notre donnée est stocké dans la case mémoire ayant l'adresse la plus faible. La donnée est donc stockée dans l'ordre inverse pour les octets.
Certains processeurs sont un peu plus souples : ils laissent le choix du boutisme. Sur ces processeurs, on peut configurer le boutisme en modifiant un bit dans un registre du processeur : il faut mettre ce bit à 1 pour du petit-boutiste, et à 0 pour du gros-boutiste, par exemple. Ces processeurs sont dits bi-boutistes.
Petit et gros-boutisme ont pour particularité que la taille des mots ne change pas vraiment l'organisation des octets. Peu importe la taille d'un mot, celui-ci se lit toujours de gauche à droite, ou de droite à gauche. Cela n’apparaît pas avec les techniques de boutismes plus compliquées.


Certains processeurs ont des boutismes plus compliqués, où chaque mot mémoire est découpé en plusieurs groupes d'octets. Il faut alors prendre en compte le boutisme des octets dans le groupe, mais aussi le boutisme des groupes eux-mêmes. On distingue ainsi un boutisme inter-groupe (le boutisme des groupes eux-même) et un boutisme intra-groupe (l'ordre des octets dans chaque groupe), tout deux pouvant être gros-boutiste ou petit-boutiste. Si l'ordre intra-groupe est identique à l'ordre inter-groupe, alors on retrouve du gros- ou petit-boutiste normal. Mais les choses changent si jamais l'ordre inter-groupe et intra-groupe sont différents. Dans ces conditions, on doit préciser un ordre d’inversion des mots mémoire (byte-swap), qui précise si les octets doivent être inversés dans un mot mémoire processeur, en plus de préciser si l'ordre des mots mémoire est petit- ou gros-boutiste.
Avantages, inconvénients et usage
[modifier | modifier le wikicode]Le choix entre petit boutisme et gros boutisme est généralement une simple affaire de convention. Il n'y a pas d'avantage vraiment probant pour l'une ou l'autre de ces deux méthodes, juste quelques avantages ou inconvénients mineurs. Dans les faits, il y a autant d'architectures petit- que de gros-boutistes, la plupart des architectures récentes étant bi-boutistes. Précisons que le jeu d'instruction x86 est de type petit-boutiste.
Si on quitte le domaine des jeu d'instruction, les protocoles réseaux et les formats de fichiers imposent un boutisme particulier. Les protocoles réseaux actuels (TCP-IP) sont de type gros-boutiste, ce qui impose de convertir les données réseaux avant de les utiliser sur les PC modernes. Et au passage, si le gros-boutisme est utilisé dans les protocoles réseau, alors que le petit-boutisme est roi sur le x86, c'est pour des raisons pratiques, que nous allons aborder ci-dessous.
Le gros-boutisme est très facile à lire pour les humains. Les nombres en gros-boutistes se lisent de droite à gauche, comme il est d'usage dans les langues indo-européennes, alors que les nombres en petit boutistes se lisent dans l'ordre inverse de lecture. Pour la lecture en hexadécimal, il faut inverser l'ordre des octets, mais il faut garder l'ordre des chiffres dans chaque octet. Par exemple, le nombre 0x015665 (87 653 en décimal) se lit 0x015665 en gros-boutiste, mais 0x655601 en petit-boutiste. Et je ne vous raconte pas ce que cela donne avec un byte-swap...
Cette différence pose problème quand on doit lire des fichiers, du code machine ou des paquets réseau, avec un éditeur hexadécimal. Alors certes, la plupart des professionnels lisent directement les données en passant par des outils d'analyse qui se chargent d'afficher les nombres en gros-boutiste, voire en décimal. Un professionnel a à sa disposition du désassembleur pour le code machine, des analyseurs de paquets pour les paquets réseau, des décodeurs de fichiers pour les fichiers, des analyseurs de dump mémoire pour l'analyse de la mémoire, etc. Cependant, le gros-boutisme reste un avantage quand on utilise un éditeur hexadécimal, quel que soit l'usage. En conséquence, le gros-boutiste a été historiquement pas mal utilisé dans les protocoles réseaux et les formats de fichiers. Par contre, cet avantage de lecture a dû faire face à divers désavantages pour les architectures de processeur.
Le petit-boutisme peut avoir des avantages sur les architectures qui gèrent des données de taille intermédiaires entre le byte et le mot. C'est le cas sur le x86, où l'on peut décider de lire des données de 8, 16, 32, ou 64 bits à partir d'une adresse mémoire. Avec le petit-boutisme, on s'assure qu'une lecture charge bien la même valeur, le même nombre. Par exemple, imaginons que je stocke le nombre 0x 14 25 36 48 sur un mot mémoire, en petit-boutiste. En petit-boutiste, une opération de lecture reverra soit les 8 bits de poids faible (0x 48), soit les 16 bits de poids faible (0x 36 48), soit le nombre complet. Ce ne serait pas le cas en gros-boutiste, où les lectures reverraient respectivement 0x 14, 0x 14 25 et 0x 14 25 36 48. Avec le gros-boutisme, de telles opérations de lecture n'ont pas vraiment de sens. En soit, cet avantage est assez limité et n'est utile que pour les compilateurs et les programmeurs en assembleur.
Un autre avantage est un gain de performance pour certaines opérations. Les instructions en question sont les opérations où on doit additionner d'opérandes codées sur plusieurs octets; sur un processeur qui fait les calculs octet par octet. En clair, le processeur dispose d'instructions de calcul qui additionnent des nombres de 16, 32 ou 64 bit, voire plus. Mais à l'intérieur du processeur, les calculs sont faits octets par octets, l'unité de calcul ne pouvant qu'additionner deux nombres de 8 bits à la fois. Dans ce cas, le petit-boutisme garantit que l'addition des octets se fait dans le bon ordre, en commençant par les octets de poids faible pour progresser vers les octets de poids fort. En gros-boutisme, les choses sont beaucoup plus compliquées...
Pour résumer, les avantages et inconvénients de chaque boutisme sont mineurs. Le gain en performance est nul sur les architectures modernes, qui ont des unités de calcul capables de faire des additions multi-octets. L'usage d'opérations de lecture de taille variable est aujourd'hui tombé en désuétude, vu que cela ne sert pas à grand chose et complexifie le jeu d'instruction. Enfin, l'avantage de lecture n'est utile que dans situations tellement rares qu'on peut légitimement questionner son statut d'avantage. En bref, les différentes formes de boutisme se valent.