9
votes

Bit alignement pour les boostes de l'espace et des performances

dans le livre codage de jeu complet, 3ème édition, L'auteur mentionne un Technique pour réduire la taille de la structure de données et augmente les performances d'accès. En substance, il s'appuie sur le fait que vous obtenez des performances lorsque les variables des membres sont alignées sur une mémoire. Il s'agit d'une optimisation potentielle évidente que les compilateurs en profiteraient, mais en veillant à ce que chaque variable soit alignée, elles finissent par ballonner la taille de la structure de données.

ou c'était sa revendication au moins.

L'augmentation réelle des performances, selon elle, consiste à utiliser votre cerveau et à veiller à ce que votre structure soit correctement conçue pour tirer profit de la vitesse augmente tout en empêchant le gonfleur du compilateur. Il fournit l'extrait de code suivant: xxx

à l'aide des objets ci-dessus struct dans un test non spécifié Il signale une augmentation de performance de 15,6% < / Code> ( 222ms comparé à 192ms ) et une taille plus petite pour le Fasttructeur . Tout cela a du sens sur le papier pour moi, mais cela ne parvient pas à tenir sous mes tests:

 Entrez la description de l'image ici

Résultats de la même heure et (comptage pour le non utilisé [2] )!

Maintenant, si le pack #pragma (push, 1) est isolé uniquement sur Fasttructeur (ou complètement supprimé) nous voyons une différence:

Entrez la description de l'image ici

Donc, enfin, vous posez la question suivante: do compilateurs modernes (vs2010 spécifiquement ) Déjà optimiser pour l'alignement des bits, d'où le manque d'augmentation de la performance (mais augmente la taille de la structure comme un effet secondaire, comme Mike Mcshaffry a déclaré)? Ou mon test n'est-il pas assez intensément / non concluant de renvoyer des résultats significatifs?

Pour les tests, j'ai réalisé une variété de tâches des opérations mathématiques, une multitude de colonne multi-dimensions traverser / vérification, des opérations matricielles, etc. . Sur le membre non aligné __ int64 . Aucun d'entre eux n'a produit de résultats différents pour l'une ou l'autre structure.

à la fin, même si leur n'était pas une augmentation de la performance, cela reste une ttrait utile pour garder à l'esprit un minimum d'utilisation de la mémoire. Mais j'aimerais l'aimer s'il y avait une performance boost (peu importe la mineure) que je ne vois tout simplement pas.


6 commentaires

Le fait que vous obteniez exactement la même période pour tous les tests que vous ne courez pas assez longtemps. La résolution du code de synchronisation n'est probablement pas suffisamment élevée pour montrer des différences.


Peut-être que pendant vos tests, la variable en question était utilisée tellement qu'elle était mise en cache dans un registre. Avoir une variable INT64 transversalement une limite de mémoire où il nécessiterait deux instructions de montage pour la récupérer serait nécessairement plus lente.


@Bopersson: Plus probablement, le compilateur les a simplement optimisé pour produire le même code.


@Bo Persson am utilise boost :: Chrono pour la mesure du temps, si cela fait une différence.


Étant donné que les deux entrent dans une cache-cache, vous verrez principalement des différences de performance lorsque vous travaillez sur plusieurs cas dans la mémoire contiguë, couvrant plusieurs cachelines. D'autre part, si l'alignement / commande appropriés est la différence entre si la structure s'inscrit dans une ou deux cachies, je pense que vous allez voir des différences de performance plus importantes.


Pour développer le commentaire de @ BOPERSSON ci-dessus, le fait que vous ayez la même heure à la microseconde, les deux cas et courir pour courir, est extrêmement suspect. Votre cadre de synchronisation est défectueux.


7 Réponses :


6
votes

Ces optimisations de la main sont généralement mortes. L'alignement n'est qu'une considération sérieuse si vous emballez pour espace ou si vous avez un type d'alignement appliqué comme des types SSE. Les règles d'alignement et d'emballage par défaut du compilateur sont intentionnellement conçues pour optimiser les performances, évidemment, et tout en le permettant de la conduire, cela ne vaut généralement pas la peine.

Probablement, dans votre programme de test, le compilateur n'a jamais enregistré de structure sur la pile et il suffit de garder les membres dans des registres, qui n'ont pas d'alignement, ce qui signifie qu'il est assez pertinent quelle est la taille ou l'alignement de la structure.

Voici la chose: il peut y avoir des aliasings et d'autres nasties avec un accès sous-mot, et il n'est pas plus lent d'accéder à un mot entier que d'accéder à un sous-mot. Donc, en général, il n'est pas plus efficace, à temps, à emballer plus étroitement que la taille des mots si vous n'ayez accès qu'à un membre.


2 commentaires

Donc, en bref, cela ne vaut pas l'effort que je n'ai absolument pas besoin de ces extra quelques octets? De plus, je n'ai pas pensé au compilateur les garder simplement dans des registres.


@ssell: De telles optimisations ne deviennent de plus en plus, et plus communes. Et oui, ça ne vaut pas la peine en général.



3
votes

Visual Studio est un excellent compilateur lorsqu'il s'agit d'optimisation. Cependant, gardez à l'esprit que la "guerre d'optimisation" actuelle dans le développement de jeux n'est pas sur le PC Arena. Bien que de telles optimisations puissent très bien être mortes sur le PC, sur les plates-formes de la console, il s'agit d'une paire de chaussures complètement différente.

Cela dit, vous voudrez peut-être republier cette question sur le site spécialisé gamedev Stackexchange site , vous pouvez obtenir des réponses directement à partir de "Le champ ".

Enfin, vos résultats sont exactement les mêmes jusqu'à la microseconde morts impossible sur un système multithreadisé moderne - je suis à peu près sûr que vous utilisez une minuterie très basse résolution ou votre timing le code est cassé.


1 commentaires

Pour chronométrage, j'utilise boost :: chrono et il suffit de soustraire les heures du système. Étant donné que les auteurs ont des résultats variés si grandement (30ms!) Je n'ai pas anticipé quelque chose de plus précisé. De plus, merci d'avoir souligné le fait de la programmation de la console. Parfois, j'oublie à quel point ils doivent faire pour tirer tout ce qu'ils peuvent éventuellement de ces anciens systèmes.



2
votes

Les compilateurs modernes alignent les membres sur des limites d'octets différents en fonction de la taille du membre. Voir le bas de Ceci .

Normalement, vous ne devriez vraiment pas vous soucier de la structure du rembourrage, mais si vous avez un objet qui va avoir 1000000 instances ou quelque chose que la règle du pouce est simplement de commander vos membres du plus grand au plus petit. Je ne recommanderais pas de jouer avec le rembourrage avec #pragma directives.


0 commentaires

1
votes

Le compilateur va optimiser pour la taille ou la vitesse et sauf si vous le diriez explicitement, vous ne saurez pas ce que vous obtenez. Mais si vous suivez les conseils de ce livre, vous gagnerez-vous-gagner sur la plupart des compilateurs. Mettez les choses les plus grandes, alignées, d'abord dans votre structure, puis des choses à moitié taille, puis des trucs d'octets simples, le cas échéant, ajoutent des variables factices à aligner. En utilisant des octets pour des choses qui ne doivent pas être peut-être une performance touchée de toute façon, en tant que compromis, utilisez Ints pour tout (devez connaître les avantages et les inconvénients de cela)

Le X86 a fait pour beaucoup de mauvais programmeurs et de compilateurs car il permet d'accéder non aligné. Rendre difficile pour beaucoup de gens de se déplacer vers d'autres plates-formes (qui prennent le dessus). Bien que les accès non alignés fonctionnent sur un X86, vous prenez une grave succès de performance. C'est pourquoi il est important de savoir comment les compilateurs fonctionnent en général aussi bien que celui que vous utilisez.

Avoir des caches, et comme avec les plates-formes informatiques modernes qui s'appuient sur des caches pour obtenir une sorte de performance, vous souhaitez tous les deux être alignés et emballés. La règle simple enseignée vous donne tous les deux ... En général. C'est très bon conseil. Ajout de pragmas spécifiques à compiler n'est pas aussi bon, rend le code non portable et ne prendrons pas beaucoup de recherche à travers afin de savoir à quelle fréquence le compilateur ignore le pragme ou ne fait pas ce que vous vouliez vraiment.


2 commentaires

Vous avez seulement besoin de variables factices pour aligner si vous utilisez #pragma pack pour empêcher le compilateur de faire son travail. Si vous écrivez simplement struct FastStruct {__int64 A; int b; char c; char d; }; sans aucun #pragma s le compilateur alignera tout correctement.


Je parle de manière générique. et évitez spécifiquement les pragmes, en règle générale, ne comptez pas sur eux.



1
votes

sur certaines plates-formes Le compilateur n'a pas d'option: objets de types plus gros que Char ont souvent des exigences strictes à une adresse appropriée alignée. Les exigences d'alignement sont généralement identiques à la taille de l'objet jusqu'à la taille du mot le plus important soutenu par la CPU Nativement. Qui est court nécessite généralement d'être à une adresse même, long nécessite généralement d'être à une adresse divisible par 4, double à une adresse divisible par 8, et par exemple Vecteurs SIMD à une adresse divisible par 16.

Étant donné que C et C ++ nécessitent la commande des membres dans l'ordre, la taille des structures va différer un peu sur les plates-formes correspondantes. Étant donné que les structures plus grandes entraînent effectivement plus de cache misses, des raques de la page, etc., il y aura une dégradation de performances substantielle lors de la création de structures plus importantes.

Depuis que j'ai vu une affirmation que peu importe: ça compte le plus (sinon tous) systèmes que j'utilise. Il existe des exemples simples de montrer différentes tailles. Dans quelle mesure cela affecte la performance dépend évidemment de la manière dont les structures doivent être utilisées. xxx


0 commentaires

1
votes

La norme C spécifie que les champs dans une structure doivent être alloués à des adresses croissantes. Une structure qui a huit variables de type 'intt8' et sept variables de type 'intT64', stockées dans cet ordre, prendront 64 octets (peu importe les exigences d'alignement d'une machine). Si les champs ont été commandés 'INT8', "INT64", "INT8", ... "INT64", "INT8", la structure prendrait 120 octets sur une plate-forme où les champs "INT64" sont alignés sur des frontières de 8 octets. Réorganiser les champs vous-même permettrez-leur d'être emballés plus étroitement. Les compilateurs ne seront toutefois pas de réorganiser les champs dans une structure une autorisation explicite absente de le faire, car cela pourrait changer la sémantique du programme.


0 commentaires

13
votes

Il dépend fortement du matériel.

Permettez-moi de démontrer: strong> p> xxx pré>


core i7 920 @ 3,5 GHz strong> p> xxx pré>

ok, pas beaucoup de différence. Mais il est toujours cohérent sur plusieurs pistes.
L'alignement fait donc une petite différence sur NEHALEM CORE I7. STRAND> P>


Intel Xeon X5482 Harpertown @ 3.2 GHz strong> (noyau 2 - génération xeon) p> xxx pré>

Jetez un coup d'oeil ... p>

6.2x plus rapide !!! h1>

Conclusion: p>

Vous voyez les résultats. Vous décidez si cela vaut ou non votre peine de faire ces optimisations. Strong> p>


edit: stry>

Code> #pragma Pack Code>: P>

Core I7 920 @ 3.5 GHz STRAND> P>

slow = 3.684
fast = 3.717
sum = 99999990000000000


7 commentaires

Ah, un test qui montre réellement des résultats! Sur mon ordinateur de travail plus ancien, j'ai reçu un 71% performances moyennes augmente plus de 100 tests. Avec la taille réduite et les résultats tels que ceux-ci, il serait impossible de ne pas faire ces optimisations, en particulier avec la simplicité.


Si vous laissez le #pragma pack , le compilateur gardera tout aligné afin que vous ne soit pas voir ce problème. Il s'agit donc d'un exemple de ce qui pourrait se produire si vous abusez #pragma pack .


Dis-tu sans #pragma pack Vous ne verrez pas la performance boost? Mon test du commentaire précédent était déjà sans elle. Utilisation de #pragma pack a abouti à Fasttruct effectuant réellement plus lent en moyenne de 50-200ms. Modifier Après avoir ré-exécuté le test, les résultats sont les mêmes que sans #pragma pack . Pas sur de ce dont il s'agissait.


@ssell: Dépend de la question de savoir si vous avez un problème de performance pour commencer. Il n'y a pas de point d'emballage de cette structure si vous ne les faites que deux, par exemple.


Je viens de rérir les tests sans le #pragma pack . Les numéros de base I7 sont les mêmes. Mais les résultats Core 2 Xeon sont 3.684 et 3.717 . ( Fasttruct est plus lent.) Je soupçonne que c'est parce que Fasttruct est exactement 16 octets - de sorte que la foulée d'itération à l'itération pourrait entraîner des conflits bancaires en cache.


@Deadmg L'objectif de cette question était vraiment juste de voir si cela valait la peine d'effort (minimal) pour rester conscient de l'ordre des membres. Pour la plupart des structures de données que j'utiliserai, cette optimisation fournira peu à aucun avantage car elles ne seront tout simplement pas suffisamment utilisées. Mais il y a un peu de choix que j'ai à l'esprit, ce qui pourrait en bénéficier. Vos commentaires (avec tous les autres dans ce fil) ont été inestimables pour moi.


@ssell: Il est utile de rester conscient de l'alignement lorsque vous souhaitez optimiser la taille sans utiliser #pragma pack . J'ai perçu le code de collier, qui est fortement optimisé pour l'utilisation de la vitesse et de la mémoire (il y a des champs de bit, par exemple), et pourtant pas vu un seul #pragma pack .