Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche

Outre le jeu d'instruction et l'architecture interne, les processeurs différent par la façon dont ils lisent et écrivent la mémoire. On pourrait croire qu'il n'y a pas grande différence entre processeurs dans la façon dont ils gèrent la mémoire. Mais ce n'est pas le cas : des différences existent qui peuvent avoir un effet assez important. Dans ce chapitre, on va parler de l'endianess du processeur et de son alignement mémoire : on va s'intéresser à la façon dont le processeur va repartir en mémoire les octets des données qu'il manipule. Ces deux paramètres sont surement 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.

Alignement mémoire[modifier | modifier le wikicode]

Il arrive que le bus de données ait une largeur de plusieurs mots mémoire : le processeur peut charger 2, 4 ou 8 mots mémoire d'un seul coup (parfois plus). Une donnée qui a la même taille que le bus de données est appelée un mot mémoire. Quand on veut accéder à une donnée sur un bus plus grand que celle-ci, le processeur ignore les mots mémoire en trop.

Exemple du chargement d'un octet dans un registre de trois octets.

Sur certains processeurs, il existe des restrictions sur la place de chaque mot en mémoire, restrictions résumées sous le nom d'alignement mémoire.

Sans alignement mémoire[modifier | modifier le wikicode]

Sans alignement, 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 localisée à l'adresse 4, puis lire une autre donnée de 16 bits localisée à l'adresse 5 sans aucun problème.

Chargement d'une donnée sur un processeur sans contraintes d'alignement.

Avec alignement mémoire[modifier | modifier le wikicode]

Mais d'autres processeurs imposent des restrictions dans la façon de gérer ces mots : ils imposent un alignement mémoire. Tout se passe comme si chaque mot mémoire de la mémoire avait la taille d'un mot : le processeur voit la mémoire comme si ces mots mémoire avaient la taille d'un mot, alors que ce n'est pas forcément le cas. Avec alignement, l'unité de lecture/écriture est le mot, pas le mot mémoire. Par contre, la capacité de la mémoire reste inchangée, ce qui fait que le nombre d'adresses utilisables diminue : il n'y a plus besoin que d'une adresse par mot mémoire et non par byte.

Chargement d'une donnée sur un processeur avec contraintes d'alignement.

Adresses valides et invalides[modifier | modifier le wikicode]

Avec cette technique, L'adresse d'un mot est égale à l'adresse de son byte de poids faible. Les autres bytes, ceux qui sont situées dans un mot, ne sont pas adressables. Par exemple, si on prend un mot de 8 octets, on est certain qu'une adresse sur 8 disparaitra. Prenons un exemple, afin de détailler quelque peu le propos, avec un processeur ayant des mots de 4 octets. Répertorions les adresses utilisables : on sait que l'adresse zéro, est l'adresse de l'octet de poids faible de notre premier mot. L'adresse 1 est située dans notre mot, pareil pour 2, pareil pour 3. L'adresse 4 est utilisable : c'est l'adresse du premier octet du second mot, etc. 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 notre exemple, les adresses utilisables sont multiples de la taille d'un mot. Et bien sachez que cela fonctionne aussi avec d'autres tailles de mot que 4. En fait, ça fonctionne même tout le temps ! :p Si m est la taille d'un mot, alors seules les adresses multiples de m seront utilisables.

Dans la réalité, ces blocs ont une taille égale à une puissance de deux : cela permet de faire quelques bidouilles sur le bus d'adresse pour économiser des fils. Si la taille d'un mot est égale à , seules les adresses multiples de seront utilisables. Hors, 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 et on peut se contenter de les connecter à la masse (le zéro volt vu dans le second chapitre).

Accès mémoire non-alignés[modifier | modifier le wikicode]

Maintenant imaginons un cas particulier : 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

Pour charger mon caractère dans un registre, pas de problèmes : celui-ci tient dans un mot. Il me suffit alors de charger mon mot dans un registre en utilisant une instruction de mon processeur qui charge un octet. Pour ma donnée de 2 octets, pas de problèmes non plus ! Le processeur charge le mot entier en ne sélectionne que les octets utiles, chose possible avec quelques décalage et un masque. Mais les problèmes arrivent quand il s'agit de charger l'entier. L'entier est en effet stocké sur deux mots différents, et on ne peut le charger en une seule fois : on dit que l'entier n'est pas aligné en mémoire.

Dans ce cas, il peut se passer des tas de choses 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. Mais sur d'autres processeurs, la situation devient nettement plus grave : le processeur ne peut en effet gérer ce genre d'accès mémoire dans ses circuits et considère qu'il est face à une erreur, similaire à une division par zéro ou quelque chose dans le genre. Il va alors interrompre le programme en cours d’exécution et exécuter un petit sous-programme qui gérera cette erreur. On dit que notre processeur effectue une exception matérielle. Si on est chanceux, ce programme de gestion d'erreur chargera cette donnée en deux fois : ça prend beaucoup de temps. 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 padding) 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 padding 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, ça prend un peu plus de place, et de la mémoire est gâchée inutilement. C'est pas grand chose, mais 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 aussi des situations dans lesquelles rajouter du padding est une bonne chose et permet des gains en performances assez abominables : une sombre histoire de cache dans les architectures multiprocesseurs ou multicores, mais je n'en dit pas plus. Cet alignement se gère dans certains langages (comme le C, le C++ ou l'ADA), en gérant l'ordre de déclaration de vos variables. Essayez toujours de déclarer vos variables de façon à remplir un mot intégralement ou le plus possible. Renseignez-vous sur le padding, et essayez de savoir quelle est la taille de vos données en regardant la norme de vos langages. Moralité : programmeurs, faites gaffe à bien gérer l'alignement en mémoire !

Boutisme[modifier | modifier le wikicode]

On peut introduire cet extrait 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 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).

Boutisme simple : gros et petit boutisme[modifier | modifier le wikicode]

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.

Big-endian little-endian

Boutisme avec byte-swap[modifier | modifier le wikicode]

Certains processeurs sont toutefois un peu plus sadiques : ils utilisent des mots mémoire de plusieurs octets. Dans ce cas, il faut aussi prendre en compte le boutisme des octets dans le mot mémoire, qui peut être gros-boutiste ou petit-boutiste. Si l'ordre des mots mémoire et celui des octets dans le mot mémoire est identique, alors on retrouve du gros- ou petit-boutiste normal. Mais les choses changent si jamais l'ordre des mots mémoire et celui des octets dans le mot mémoire 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. Par exemple, comparons l'ordre des octets entre un nombre codé en gros-boutiste pur, et un nombre gros-boutiste dont les octets sont rangés dans un mot mémoire en petit-boutiste. Le nombre en question est 0x 0A 0B 0C 0D, en hexadécimal, le premier mot mémoire étant indiqué en jaune, le second en blanc.

Comparaison entre boutisme avec et sans inversion de mots mémoire.