Quelle est l'importance de l'alignement de la mémoire? Est-ce toujours important?


15

Depuis quelque temps, j'ai cherché et lu beaucoup de choses sur l'alignement de la mémoire, comment cela fonctionne et comment l'utiliser. L'article le plus pertinent que j'ai trouvé pour l'instant est celui-ci .

Mais même avec cela, j'ai encore quelques questions à ce sujet:

  1. Hors du système embarqué, nous avons souvent une énorme quantité de mémoire dans notre ordinateur qui rend la gestion de la mémoire beaucoup moins critique, je suis complètement dans l'optimisation, mais maintenant, est-ce vraiment quelque chose qui peut faire la différence si nous comparons le même programme avec ou sans sa mémoire réarrangée et alignée?
  2. L'alignement de la mémoire présente-t-il d'autres avantages? J'ai lu quelque part que le CPU fonctionne mieux / plus rapidement avec la mémoire alignée car cela prend moins d'instructions à traiter (si l'un d'entre vous a un lien pour un article / un benchmark à ce sujet?), Dans ce cas, la différence est-elle vraiment significative? Y a-t-il plus d'avantages que ces deux-là?
  3. Dans le lien de l'article, au chapitre 5, l'auteur dit:

    Attention: en C ++, les classes qui ressemblent à des structures peuvent violer cette règle! (Qu'ils le fassent ou non dépend de la façon dont les classes de base et les fonctions membres virtuelles sont implémentées, et varie selon le compilateur.)

  4. L'article parle principalement des structures, mais la déclaration des variables locales est-elle également affectée par ce besoin?

    Avez-vous une idée du fonctionnement exact de l'alignement de la mémoire en C ++, car il semble y avoir quelques différences?

Cette ancienne question contient le mot "alignement", mais elle ne fournit aucune réponse aux questions ci-dessus.


Les compilateurs C ++ sont plus enclins à le faire (insérer un rembourrage là où c'est nécessaire ou bénéfique) pour vous. À partir du lien que vous avez mentionné, regardez dans la section 12 "Outils" les choses que vous pouvez utiliser.
rwong

Réponses:


11

Oui, l'alignement et la disposition de vos données peuvent faire une grande différence dans les performances, pas seulement quelques pour cent mais quelques à plusieurs centaines de pour cent.

Prenez cette boucle, deux instructions comptent si vous exécutez suffisamment de boucles.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

Avec et sans cache, et avec alignement avec et sans lancer de cache dans la prédiction de branche et vous pouvez faire varier considérablement les performances de ces deux instructions (tics du minuteur):

min      max      difference
00016DDE 003E025D 003C947F

Un test de performance que vous pouvez très facilement faire vous-même. ajouter ou supprimer des nops autour du code sous test et effectuer un travail de synchronisation précis, déplacer les instructions sous test le long d'une plage d'adresses suffisamment large pour toucher les bords des lignes de cache, etc.

Même chose avec les accès aux données. Certaines architectures se plaignent des accès non alignés (effectuant une lecture 32 bits à l'adresse 0x1001 par exemple), en vous donnant un défaut de données. Certains de ceux que vous pouvez désactiver la faute et prendre le coup de performance. D'autres, qui permettent des accès non alignés, vous obtiennent juste les performances.

Ce sont parfois des "instructions" mais la plupart du temps ce sont des cycles horloge / bus.

Regardez les implémentations memcpy dans gcc pour diverses cibles. Supposons que vous copiez une structure de 0x43 octets, vous pouvez trouver une implémentation qui copie un octet en laissant 0x42, puis copie 0x40 octets en gros morceaux efficaces, puis le dernier 0x2, il peut faire deux octets individuels ou un transfert de 16 bits. L'alignement et la cible entrent en jeu si les adresses source et de destination sont sur le même alignement, par exemple 0x1003 et 0x2003, alors vous pouvez faire un octet, puis 0x40 en gros morceaux puis 0x2, mais si l'un est 0x1002 et l'autre 0x1003, alors il obtient vraiment moche et très lent.

La plupart du temps, ce sont des cycles de bus. Ou pire le nombre de transferts. Prenez un processeur avec un bus de données de 64 bits, comme ARM, et effectuez un transfert de quatre mots (lecture ou écriture, LDM ou STM) à l'adresse 0x1004, c'est-à-dire une adresse alignée sur les mots et parfaitement légale, mais si le bus est 64 bits de large, il est probable que l'instruction unique se transforme en trois transferts dans ce cas, un 32 bits à 0x1004, un 64 bits à 0x1008 et un 32 bits à 0x100A. Mais si vous aviez la même instruction mais à l'adresse 0x1008, il pourrait effectuer un seul transfert de quatre mots à l'adresse 0x1008. Chaque transfert est associé à une heure de configuration. Ainsi, la différence d'adresse 0x1004 à 0x1008 en elle-même peut être plusieurs fois plus rapide, même / esp lors de l'utilisation d'un cache et tous sont des hits de cache.

En parlant de cela, même si vous faites une lecture de deux mots à l'adresse 0x1000 vs 0x0FFC, le 0x0FFC avec des échecs de cache va provoquer deux lectures de ligne de cache où 0x1000 est une ligne de cache, vous avez quand même la pénalité d'une ligne de cache lue pour un hasard accès (lecture de plus de données que l'utilisation) mais cela double. La façon dont vos structures sont alignées ou vos données en général et votre fréquence d'accès à ces données, etc., peuvent entraîner un contournement du cache.

Vous pouvez finir par répartir vos données de telle sorte que lorsque vous traitez les données, vous pouvez créer des expulsions, vous pourriez ne pas avoir de chance et finir par n'utiliser qu'une fraction de votre cache et au fur et à mesure que vous la parcourez, la prochaine goutte de données entre en collision avec une goutte précédente . En mélangeant vos données ou en réorganisant les fonctions dans le code source, etc., vous pouvez créer ou supprimer des collisions, car tous les caches ne sont pas créés égaux, le compilateur ne va pas vous aider ici, c'est sur vous. Même la détection du succès ou de l'amélioration des performances vous appartient.

Toutes les choses que nous avons ajoutées pour améliorer les performances, les bus de données plus larges, les pipelines, les caches, la prédiction de branche, les unités / chemins d'exécution multiples, etc. aideront le plus souvent, mais ils ont tous des points faibles, qui peuvent être exploités intentionnellement ou accidentellement. Il y a très peu de choses que le compilateur ou les bibliothèques peuvent faire à ce sujet, si vous êtes intéressé par les performances que vous devez régler et l'un des plus grands facteurs de réglage est l'alignement du code et des données, pas seulement aligné sur 32, 64, 128, 256 les limites de bits, mais aussi lorsque les choses sont relatives les unes aux autres, vous voulez que les boucles fortement utilisées ou les données réutilisées ne se retrouvent pas de la même manière dans le cache, elles veulent chacune la leur. Les compilateurs peuvent aider, par exemple, à ordonner des instructions pour une architecture super scalaire, à réorganiser des instructions qui n'ont pas d'importance les unes par rapport aux autres,

Le plus gros oubli est l'hypothèse que le processeur est le goulot d'étranglement. Cela n'a pas été vrai depuis une décennie ou plus, l'alimentation du processeur est le problème et c'est là que des problèmes tels que les performances d'alignement, le cache du cache, etc. entrent en jeu. Avec un peu de travail même au niveau du code source, réorganiser les données dans une structure, ordonner les déclarations de variable / struct, ordonner les fonctions dans le code source et un peu de code supplémentaire pour aligner les données, peut améliorer les performances plusieurs fois ou plus.


+1 si ce n'est que pour votre dernier paragraphe. La bande passante mémoire est le problème le plus critique pour quiconque tente d'écrire du code rapide aujourd'hui, pas le nombre d'instructions. Et cela signifie qu'il est extrêmement important d'optimiser les choses pour réduire les erreurs de cache, ce qui peut être fait en modifiant l'alignement dans de nombreuses circonstances.
Jules

Si votre code et vos données sont mis en cache et que vous effectuez suffisamment de boucles / cycles sur ces données, le nombre d'instructions et l'emplacement des instructions dans une ligne de récupération, où les branches atterrissent dans le canal par rapport à ce sur quoi elles comptent, importent. Mais dans les systèmes basés sur dram et / ou flash, vous devez d'abord vous soucier d'alimenter le processeur oui.
old_timer

15

Oui, l'alignement de la mémoire est toujours important.

Certains processeurs ne peuvent en fait pas effectuer de lecture sur des adresses non alignées. Si vous utilisez un tel matériel et que vous stockez vos entiers non alignés, vous devrez probablement les lire avec deux instructions suivies de quelques autres instructions pour placer les divers octets aux bons endroits afin de pouvoir réellement les utiliser. . Les données alignées sont donc essentielles aux performances.

La bonne nouvelle est que vous n'avez généralement pas à vous en soucier. Presque n'importe quel compilateur pour presque toutes les langues produira un code machine qui respecte les exigences d'alignement du système cible. Vous ne devez commencer à y penser que si vous prenez le contrôle direct de la représentation en mémoire de vos données, ce qui n'est pas nécessaire aussi souvent qu'auparavant. C'est une chose intéressante à savoir et absolument indispensable de savoir si vous voulez comprendre l'utilisation de la mémoire à partir des différentes structures que vous créez, et comment peut-être réorganiser les choses pour être plus efficaces (en évitant le remplissage). Mais à moins que vous n'ayez besoin de ce type de contrôle (et pour la plupart des systèmes, vous n'en avez tout simplement pas), vous pouvez passer une carrière entière sans le savoir ou sans vous en soucier.


1
En particulier, ARM ne prend pas en charge l'accès non aligné. Et c'est le CPU presque tout ce que le mobile utilise.
Jan Hudec

Notez également que Linux émule un accès non aligné à un certain coût d'exécution, mais que Windows (CE et Phone) ne le fait pas et qu'une tentative d'accès non aligné plantera simplement l'application.
Jan Hudec

2
Bien que cela soit principalement vrai, notez que certaines plates-formes (y compris x86) ont des exigences d'alignement différentes en fonction des instructions qui seront utilisées , ce qui n'est pas facile pour le compilateur de fonctionner lui-même, vous devez donc parfois pad pour vous assurer certaines opérations (par exemple les instructions SSE, dont beaucoup nécessitent un alignement sur 16 octets) peuvent être utilisées pour certaines opérations. De plus, l'ajout d'un remplissage supplémentaire afin que deux éléments fréquemment utilisés ensemble se produisent sur la même ligne de cache (également 16 octets) peut avoir un effet énorme sur les performances dans certains cas et n'est pas non plus automatisé.
Jules

3

Oui, cela compte toujours, et dans certains algorithmes critiques pour les performances, vous ne pouvez pas compter sur le compilateur.

Je ne citerai que quelques exemples:

  1. De cette réponse :

Normalement, le microcode récupérera la quantité appropriée de 4 octets de la mémoire, mais s'il n'est pas aligné, il devra extraire deux emplacements de 4 octets de la mémoire et reconstruire la quantité de 4 octets souhaitée à partir des octets appropriés des deux emplacements

  1. L'ensemble d'instructions SSE nécessite un alignement spécial. S'il n'est pas respecté, vous devez utiliser des fonctions spéciales pour charger et stocker les données dans la mémoire non alignée. Cela signifie deux instructions supplémentaires.

Si vous ne travaillez pas sur des algorithmes critiques pour les performances, oubliez simplement les alignements de mémoire. Ce n'est pas vraiment nécessaire pour une programmation normale.


1

Nous avons tendance à éviter les situations où cela est important. Si c'est important, ça compte. Les données non alignées se produisaient par exemple lors du traitement des données binaires, ce qui semble être évité de nos jours (les gens utilisent beaucoup XML ou JSON).

Si vous parvenez à créer un tableau non aligné d'entiers, alors sur un processeur Intel typique, votre code traite ce tableau un peu plus lentement que pour les données alignées. Sur un processeur ARM, il s'exécute un peu plus lentement si vous dites au compilateur que les données ne sont pas alignées. Il peut soit s'exécuter terriblement, beaucoup plus lentement ou donner de mauvais résultats, selon le modèle de processeur et le système d'exploitation, si vous utilisez des données non alignées sans en informer le compilateur.

Explication de la référence à C ++: En C, tous les champs d'une structure doivent être stockés dans l'ordre croissant de la mémoire. Donc, si vous avez des champs char / double / char et que vous voulez que tout soit aligné, vous aurez un octet, sept octets inutilisés, huit octets doubles, un octet char, sept octets inutilisés. Dans les structures C ++, c'est la même chose pour la compatibilité. Mais pour les structures, le compilateur peut réorganiser les champs, vous pouvez donc avoir un caractère octet, un autre caractère octet, six octets inutilisés, 8 octets double. Utilisation de 16 au lieu de 24 octets. Dans les structures C, les développeurs évitent généralement cette situation et ont les champs dans un ordre différent en premier lieu.


1
Les données non alignées se produisent en mémoire. Les programmes qui n'ont pas de structures de données correctement compressées peuvent subir d'énormes pénalités en termes de performances, même pour un ordre de valeurs apparemment sans conséquence. Dans le code lthread, par exemple, deux valeurs dans une seule ligne de cache provoquent des blocages de pipeline massifs lorsque deux threads y accèdent en même temps (en ignorant les problèmes de sécurité des threads, bien sûr).
greyfade

Un compilateur C ++ peut réorganiser les champs dans certaines conditions uniquement, ce qui n'est probablement pas respecté si vous n'êtes pas au courant de ces règles. En plus de cela, je ne connais aucun compilateur C ++ qui utilise réellement cette liberté.
Sjoerd

1
Je n'ai jamais vu de champs de réorganisation du compilateur C. J'ai vu beaucoup d'insertions et d'alignement entre les caractères / ints par exemple.
PaulHK

1

De nombreux bons points sont déjà mentionnés dans les réponses ci-dessus. Juste pour ajouter, même dans des systèmes non intégrés qui traitent de la recherche / exploration de données, les performances des questions de mémoire et les temps d'accès sont si importants que le code d'assemblage autre que l'alignement est écrit pour cela.

Je recommande également une lecture intéressante: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf


1

Quelle est l'importance de l'alignement de la mémoire? Est-ce toujours important?

Oui. Non, cela dépend.

Hors du système embarqué, nous avons souvent une énorme quantité de mémoire dans notre ordinateur qui rend la gestion de la mémoire beaucoup moins critique, je suis complètement dans l'optimisation, mais maintenant, est-ce vraiment quelque chose qui peut faire la différence si nous comparons le même programme avec ou sans sa mémoire réarrangée et alignée?

Votre application aura une empreinte mémoire plus petite et fonctionnera plus rapidement si elle est correctement alignée. Dans l'application de bureau typique, cela n'aura pas d'importance en dehors de cas rares / atypiques (comme votre application se terminant toujours par le même goulot d'étranglement des performances et nécessitant des optimisations). Autrement dit, l'application sera plus petite et plus rapide si elle est correctement alignée, mais dans la plupart des cas pratiques, elle ne devrait pas affecter l'utilisateur d'une manière ou d'une autre.

L'alignement de la mémoire présente-t-il d'autres avantages? J'ai lu quelque part que le CPU fonctionne mieux / plus rapidement avec la mémoire alignée car cela prend moins d'instructions à traiter (si l'un d'entre vous a un lien pour un article / un benchmark à ce sujet?), Dans ce cas, la différence est-elle vraiment significative? Y a-t-il plus d'avantages que ces deux-là?

Ça peut être. C'est quelque chose à (éventuellement) garder à l'esprit lors de l'écriture de code, mais dans la plupart des cas, cela ne devrait tout simplement pas avoir d'importance (c'est-à-dire que j'arrange toujours mes variables membres par empreinte mémoire et fréquence d'accès - ce qui devrait faciliter la mise en cache - mais je le fais pour facilité d'utilisation / lecture et refactorisation du code, pas à des fins de mise en cache).

Avez-vous une idée du fonctionnement exact de l'alignement de la mémoire en C ++, car il semble y avoir quelques différences?

J'ai lu à ce sujet quand les trucs alignof sont sortis (C ++ 11?) Je ne m'en suis pas inquiété depuis (je fais principalement des applications de bureau et le développement de serveurs backend ces jours-ci).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.