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.