53 votes

C ++: optimisation de l'ordre des membres?

J'ai lu le post de blog par un jeu de coder pour l'Introversion et il est occupé à essayer de serrer tous les CPU tique qu'il peut sortir de la code. Un truc qu'il mentionne la main gauche est à

"ré-ordonner les variables membres d'une classe en plus utilisés et les moins utilisées."

Je ne suis pas familier avec le C++, ni avec la façon dont il compile, mais je me demandais si

  1. Cette déclaration est exacte?
  2. Comment/Pourquoi?
  3. Il ne s'applique pas à d'autres (compilé/script) langues?

Je suis conscient que le montant de la centrale (CPU) et le temps gagné par cette astuce serait minime, c'est pas un deal-breaker. Mais d'un autre côté, dans la plupart des fonctions qu'il serait assez facile d'identifier les variables qui vont être les plus couramment utilisés, et juste commencer à coder de cette manière par défaut.

66voto

Steve Jessop Points 166970

Deux questions se posent ici:

  • Si, et quand, en gardant certains domaines est une optimisation.
  • Comment faire réellement le faire.

La raison qu'il pourrait aider, c'est que la mémoire est chargée dans le cache du PROCESSEUR en morceaux appelés "lignes de cache". Cela prend du temps, et d'une manière générale, plus les lignes de cache chargé de votre objet, le plus il faut. Aussi, plus d'autres trucs obtient jeté hors de la cache pour faire de la place, ce qui ralentit un autre code d'une manière imprévisible.

De la taille d'une ligne de cache dépend du processeur. Si elle est grande par rapport à la taille de vos objets, puis très peu d'objets allons étendre sur une limite de ligne de cache, de sorte que l'optimisation de l'ensemble est assez hors de propos. Sinon, vous pourriez sortir avec parfois seulement une partie de votre objet dans le cache, et le reste dans la mémoire principale (ou L2 cache, peut-être). C'est une bonne chose si les opérations les plus communes (celles dont l'accès le plus couramment utilisé champs) utiliser peu de cache que possible de l'objet, de sorte que le regroupement de ces champs ensemble vous donne une meilleure chance que cela se produise.

Le principe général est appelé "la localité de référence". Le rapprochement des différentes adresses de la mémoire sont que votre programme accède à la, meilleures sont vos chances d'obtenir de bons cache comportement. Il est souvent difficile de prédire les performances à l'avance: processeurs différents modèles de la même architecture peut se comporter différemment, multi-threading signifie que vous ne savent souvent pas ce qui va être dans le cache, etc. Mais il est possible de parler de ce qui est susceptible de se produire, la plupart du temps. Si vous voulez savoir quoi que ce soit, vous avez généralement à mesurer.

Veuillez noter qu'il y a quelques erreurs ici. Si vous utilisez le PROCESSEUR opérations atomiques (dont les types atomiques dans C++0x, en général), vous pouvez constater que le CPU verrouille l'ensemble de la ligne de cache afin de verrouiller le champ. Alors, si vous avez plusieurs atomique champs proches, avec les différents threads s'exécutant sur différents cœurs et d'exploitation sur les différents domaines dans le même temps, vous constaterez que toutes ces opérations atomiques sont sérialisés parce qu'ils ont tous verrouiller le même emplacement de mémoire, même s'ils sont en train d'exploitation sur les différents champs. S'ils avaient été d'exploitation sur les différentes lignes de cache puis ils ont travaillé en parallèle et courir plus vite. En fait, comme Glen (par Herb Sutter) souligne dans sa réponse, sur un programme cohérent de la mémoire cache de l'architecture de ce qui se passe, même sans les opérations atomiques, et peut complètement ruiner votre journée. Donc, la localité de référence n'est pas nécessairement une bonne chose lorsque plusieurs cœurs sont impliqués, même si elles partagent le cache. Vous pouvez vous attendre à être, au motif que le cache habituellement sont une source de perte de vitesse, mais horriblement mal dans votre cas particulier.

Maintenant, au-delà de la distinction entre couramment utilisé et le moins utilisé des champs, plus un objet est, le moins de mémoire (et donc moins de cache), elle occupe la. C'est presque une bonne nouvelle pour tous, au moins où vous n'avez pas lourd conflit. La taille d'un objet dépend du champs, et sur tout remplissage qui doit être inséré entre les champs afin de s'assurer qu'ils sont correctement alignés pour l'architecture. C++ (parfois) met des contraintes sur l'ordre des champs qui doivent apparaître dans un objet, basé sur l'ordre où elles sont déclarées. C'est pour rendre la programmation plus facile. Donc, si votre objet contient:

  • un int (4 octets, 4-alignés)
  • suivi par un char (1 octet, tout d'alignement)
  • suivi par un int (4 octets, 4-alignés)
  • suivi par un char (1 octet, tout d'alignement)

alors les chances sont ce occupera de 16 octets en mémoire. La taille et l'alignement de type int n'est pas le même sur chaque plate-forme, par le chemin, mais la 4 est très fréquent et c'est juste un exemple.

Dans ce cas, le compilateur va insérer 3 octets de remplissage avant la seconde int, aligner correctement, et 3 octets de remplissage à la fin. Un objet de la taille doit être un multiple de son alignement, de sorte que les objets du même type peut être placée à côté de la mémoire. C'est tout un tableau est en C/C++, à côté des objets en mémoire. A la struct été int, int, char, char, puis le même objet pourrait avoir été 12 octets, parce que char n'a pas d'alignement exigence.

J'ai dit que si int est 4-alignés est dépend de la plateforme: sur les BRAS, il doit absolument être, puisque non alignés accès lève une exception de matériel. Sur x86, vous pouvez accéder à ints aléatoire, mais il est généralement plus lent et IIRC non-atomique. Si les compilateurs généralement (toujours?) 4-align ints sur x86.

La règle de base lors de l'écriture de code, si vous vous souciez de l'emballage, est de regarder l'alignement des exigences de chaque membre de la structure. Puis commander les champs avec le plus grand aligné d'abord, puis la plus petite suivante, et ainsi de suite, jusqu'aux membres n'ayant pas aligment exigence. Par exemple si je suis en train d'écrire du code portable, je pourrais venir avec ceci:

struct some_stuff {
    double d;   // I expect double is 64bit IEEE, it might not be
    uint64_t l; // 8 bytes, could be 8-aligned or 4-aligned, I don't know
    uint32_t i; // 4 bytes, usually 4-aligned
    int32_t j;  // same
    short s;    // usually 2 bytes, could be 2-aligned or unaligned, I don't know
    char c[4];  // array 4 chars, 4 bytes big but "never" needs 4-alignment
    char d;     // 1 byte, any alignment
};

Si vous ne connaissez pas l'alignement d'un champ, ou que vous soyez à écrire du code portable mais de faire le mieux que vous pouvez, sans tricherie, alors vous supposez que l'alignement exigence est la plus grande exigence de tout type fondamental dans la structure, et que l'alignement exigence de types fondamentaux, c'est leur taille. Donc, si votre structure contient un uint64_t, ou un long, alors la meilleure supposition est que c'est 8-alignés. Parfois, vous serez en tort, mais vous serez une grande partie du temps.

Notez que les jeux de programmeurs comme votre blogueur souvent tout savoir sur leur processeur et de matériel, et donc ils n'ont pas à deviner. Ils savent que la taille de ligne de cache, ils savent que la taille et l'alignement de chaque type, et ils connaissent la structure de mise en page des règles utilisées par leurs compilateur (pour la nacelle et de la non-POD types). S'il prend en charge de multiples plates-formes, ils peuvent le cas pour chacun d'eux si nécessaire. Ils passent aussi beaucoup de temps à réfléchir sur les objets dans leur jeu va bénéficier d'améliorations de performances, et à l'aide de profileurs pour savoir où sont les véritables goulets d'étranglement. Mais même ainsi, ce n'est pas une mauvaise idée d'avoir quelques règles de base que vous appliquez si l'objet a besoin ou pas. Tant qu'il ne fera pas le code incertaine "mettre utilisées couramment dans les champs au début de l'objet" et "trier par alignement obligation" sont deux bonnes règles.

12voto

Glen Points 13521

Selon le type de programme que vous utilisez ces conseils peuvent entraîner une augmentation de la performance ou il peut ralentir les choses de façon drastique.

Faire cela dans un programme multi-threadé signifie que vous allez augmenter les risques de fausse-partage".

Découvrez Herbe Sutters articles sur le sujet ici

Je l'ai dit avant et je vais continuer à le dire. Le seul véritable moyen d'obtenir un réel gain de performances pour mesurer votre code, et d'utiliser des outils pour identifier le véritable goulot de bouteille au lieu d'arbitraire, de changer des choses dans votre base de code.

6voto

Canopus Points 3154

C'est l'un des moyens d'optimiser la taille du groupe de travail . John Robbins a écrit un bon article sur la manière dont vous pouvez accélérer les performances de l'application en optimisant la taille du jeu de travail. Bien entendu, cela implique une sélection minutieuse des cas d'utilisation les plus fréquents que l'utilisateur final est susceptible de réaliser avec l'application.

3voto

leander Points 6363

Nous avons légèrement différentes lignes directrices pour les membres (BRAS de l'architecture cible, surtout le POUCE 16 bits codegen pour diverses raisons):

  • groupe par alignement des exigences (ou, pour les débutants, "le groupe "taille" fait habituellement le tour)
  • plus petit premier

"groupe par l'alignement" est un peu évident, et en dehors de la portée de cette question; il évite de rembourrage, utilise moins de mémoire, etc.

La deuxième balle, cependant, découle de la petite de 5 bits "immédiate" de la taille du champ sur le POUCE LDRB (Charge de Registre Octet), LDRH (Charge Inscrire Halfword), et LDR (Charge de Registre) des instructions.

5 bits signifie que les décalages de 0 à 31, peuvent être encodées. Effectivement, en supposant que "cela" est à portée de main dans un registre (ce qui est souvent le cas):

  • Les octets de 8 bits peut être chargé dans une instruction si elles existent à ce+0 par le biais de ce+31
  • 16 bits halfwords si elles existent à ce+0 par le biais de ce+62;
  • Machine 32 bits des mots, s'ils existent, à ce+0 par le biais de ce+124.

Si ils sont en dehors de cette plage, plusieurs instructions doivent être générés: soit une séquence de Ajoute avec immédiates pour accumuler de l'adresse appropriée dans un registre, ou pire encore, une charge à partir du sens littéral de la piscine à la fin de la fonction.

Si nous ne frappe pas la traduction littérale de la piscine, ça fait mal: le sens littéral de la piscine passe par la d-cache, pas le je-cache; cela signifie qu'au moins un cacheline de dollars de charges à partir de la mémoire principale pour la première littéral de l'accès à la piscine, puis un hôte potentiel d'éviction et d'invalidation des problèmes entre le cache et je cache si le littéral de la piscine ne démarre pas sur sa propre ligne de cache (c'est à dire si le code n'a pas de fin à la fin d'une ligne de cache).

(Si j'avais un peu de voeux pour le compilateur que nous allons travailler, un moyen de forcer littérale piscines pour démarrer sur cacheline frontières serait l'un d'eux.)

(Unrelatedly, l'une des choses que nous faisons pour éviter littérale utilisation du pool est de garder tous nos "globals" dans une table unique. Cela signifie un littéral de la piscine de recherche pour le "GlobalTable", plutôt que de multiples recherches pour chaque mondial. Si vous êtes vraiment intelligent, vous pourriez être en mesure de garder votre GlobalTable dans une sorte de mémoire qui peut être consulté sans chargement d'un littéral de la piscine entrée -- il était .sbss?)

2voto

Lou Franco Points 48823

Eh bien, le premier membre n'a pas besoin d'un décalage ajouté au pointeur pour y accéder.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X