L'ESS, intrinsèques, et l'alignement
J'ai écrit un vecteur 3D de classe à l'aide d'une beaucoup de l'ESS compilateur intrinsèques. Tout a bien fonctionné jusqu'à ce que j'ai commencé à démarrent des instances des classes ayant le vecteur 3D en tant que membre de de nouveau. J'ai vécu étrange se bloque en mode release, mais pas en mode debug et dans l'autre sens.
J'ai donc lu quelques articles et pensé que j'avais besoin d'aligner les classes de posséder un exemple de vecteur 3D de classe à 16 octets trop. Donc j'ai juste ajouté _MM_ALIGN16
(__declspec(align(16)
) devant les classes comme suit:
_MM_ALIGN16 struct Sphere
{
//....
Vector3 point;
float radius
};
Qui semble résoudre le problème au premier abord. Mais après la modification du code de mon programme a commencé à planter dans d'étranges façons de nouveau. J'ai cherché sur le web et trouvé un blog article. J'ai essayé ce que l'auteur, Ernst Chaud, fait pour résoudre le problème et ça fonctionne pour moi aussi. J'ai ajouté le nouveau et supprimer des opérateurs de mes classes comme ceci:
_MM_ALIGN16 struct Sphere
{
//....
void *operator new (unsigned int size)
{ return _mm_malloc(size, 16); }
void operator delete (void *p)
{ _mm_free(p); }
Vector3 point;
float radius
};
Ernst mentionne que cette approche pourrait être problématique, mais il a juste des liens vers un forum qui n'existe plus, sans expliquer pourquoi il pourrait être problématique.
Donc mes questions sont:
-
Quel est le problème avec la définition des opérateurs?
-
Pourquoi n'est-il pas d'ajouter
_MM_ALIGN16
à la définition de classe assez? -
Quelle est la meilleure façon de traiter les problèmes d'alignement à venir avec l'ESS intrinsèques?
- Dans le premier cas, vous êtes répartition des structures sur la pile ou le tas? Je ne suis pas sûr malloc renvoie aligné mémoire par défaut, tandis que les _mm_malloc certainement ne serait - ce que voulez-vous dire par "après un moment, mon programme a commencé à planter de nouveau"? Voulez-vous dire après l'avoir quitté en cours d'exécution pour un peu (et ce qui était il en train de faire)?
- Les problèmes ont commencé quand j'ai commencé à l'allocation de leurs structures sur le tas. Avec la "après un certain temps", phrase, je veux dire qu'il a commencé à s'écraser après j'ai changé le code. Je suppose que l'alignement a droite par accident, puis je l'ai détruite. Je pense que malloc n'a pas de retour de mémoire de 16 octets alignés qui est le problème, je suppose. Ma question est de savoir vraiment quel est le problème avec l'opérateur approche et quelle est la meilleure façon de gérer le code à l'aide de l'ESS intrinsèques.
- En fait, vous n'avez pas besoin de spécifier l'alignement de
Sphere
(à l'aide de cette_MM_ALIGN16
chose), car le compilateur est assez intelligent pour voir queSphere
16-alignés membre et ajuste automatiquementSphere
's l'alignement des exigences (étant donné queVector3
est correctement aligné). C'est la raison pour laquelle vous n'avez pas explicitement à alignerVector3
soit si elle a déjà un__m128
membre. C'est seulement l'allocation dynamique, c'est un problème et cela peut être surmonté par la surchargeoperator new/delete
, comme écrit dans le blog (et les autres choses, comme spécialiséestd::allocator
).
Vous devez vous connecter pour publier un commentaire.
Tout d'abord, vous devez prendre soin de deux types d'allocation de mémoire:
Allocation statique. Pour les variables automatiques à être aligné correctement, votre type a besoin d'un bon alignement de spécification (par exemple
__declspec(align(16))
,__attribute__((aligned(16)))
, ou votre_MM_ALIGN16
). Mais heureusement, vous avez seulement besoin de ce si les exigences alignement donné par le type de membres (le cas échéant) ne sont pas suffisantes. Si vous n'en avez pas besoin pour vousSphere
, étant donné que votreVector3
est déjà aligné correctement. Et si votreVector3
contient un__m128
membre (ce qui est assez probable, sinon je conseille de le faire), alors vous n'avez même pas besoin deVector3
. Vous n'avez généralement pas avoir à jouer avec l'compilateur spécifique attributs d'alignement.L'allocation dynamique. Voilà pour la partie la plus facile. Le problème, c'est que le C++ utilise, sur le niveau le plus bas, plutôt de type agnostique fonction d'allocation mémoire pour l'allocation dynamique de la mémoire. Cela ne garantit l'alignement approprié pour tous les types standard, ce qui pourrait arriver à être de 16 octets, mais n'est pas garanti.
Pour ce pour compenser, vous devez surcharger la builtin
operator new/delete
pour mettre en œuvre votre propre allocation de mémoire et d'utiliser l'alignement de l'allocation de fonction sous le capot au lieu de la bonne vieillemalloc
. La surchargeoperator new/delete
est un sujet sur son propre, mais n'est pas si difficile que cela puisse paraître au premier abord (bien que votre exemple n'est pas suffisant) et vous pouvez lire à ce sujet dans cette excellente question dans la FAQ.Malheureusement vous avez à faire cela pour chaque type qui a un membre qui a besoin de la non-standard de l'alignement, dans votre cas, les deux
Sphere
etVector3
. Mais ce que vous pouvez faire pour le rendre un peu plus facile est de simplement faire une vide de la classe de base avec une bonne surcharges pour les opérateurs et puis il suffit de tirer toutes les classes de cette classe de base.Ce que la plupart des gens ont parfois tendance à oublier, c'est que la norme de l'allocateur
std::alocator
utilise le globaloperator new
pour l'allocation de la mémoire, de sorte que votre types ne fonctionne pas avec les conteneurs standard (et unstd::vector<Vector3>
n'est pas rare, un cas d'utilisation). Ce que vous devez faire est de faire votre propre standard conforme allocation et l'utilisation de ce. Mais pour des raisons de commodité et de sécurité, il est effectivement préférable de spécialiserstd::allocator
pour votre type (peut-être juste en le dérivant de formulaire personnalisé de votre allocateur), de sorte qu'il est toujours utilisé, et vous n'avez pas besoin de soins pour l'utilisation de la bonne allocateur chaque fois que vous utilisez unstd::vector
. Malheureusement, dans ce cas, vous avez à nouveau se spécialisent pour chaque aligné type, mais un petit mal de macro vous aide à cela.En outre, vous avez à regarder dehors pour d'autres choses en utilisant le global
operator new/delete
au lieu de votre propre, commestd::get_temporary_buffer
etstd::return_temporary_buffer
, et des soins pour les personnes si nécessaire.Malheureusement, il n'est pas encore une bien meilleure approche de ces problèmes, je pense, sauf si vous êtes sur une plate-forme nativement aligne à 16 et savoir à propos de cette. Ou vous pourriez juste surcharger le mondial
operator new/delete
de toujours aligner chaque bloc de mémoire de 16 octets, et être libre de prendre soin de l'alignement de chaque et chaque classe contenant un membre de l'ESS, mais je ne connais pas les implications de cette approche. Dans le pire des cas, il devrait juste de gaspiller de la mémoire, mais encore une fois vous avez l'habitude de ne pas allouer de petits objets de manière dynamique en C++ (bien questd::list
etstd::map
pourrait penser différemment à ce sujet).Donc, pour résumer:
De soins pour un bon alignement de la mémoire statique en utilisant des choses comme
__declspec(align(16))
, mais seulement si elle n'est pas déjà soigné par un membre, ce qui est généralement le cas.Surcharge
operator new/delete
pour chaque type ayant un membre non-standard de l'alignement des exigences.Faire un cunstom norme-conforme au programme d'allocation pour l'utilisation dans des conteneurs standard de alignées types, ou mieux encore, se spécialisent
std::allocator
pour chaque aligné type.Enfin, certains conseils généraux. Souvent, on ne profiter de l'ESS dans le calcul-lourds blocs lors de l'exécution de nombreux vecteur des opérations. Pour simplifier tout cela les problèmes d'alignement, en particulier les problèmes de la protection de l'alignement de chaque et chaque type de contenant un
Vector3
, il pourrait être une bonne approche pour faire un spécial ESS type de vecteur et de les utiliser uniquement à l'intérieur de longs calculs, à l'aide d'un normal, non-ESS, vecteur de stockage et de variables de membre.std::aligned_storage
à partir de C++11 activer tout cela sans la nécessité d'une spécialiste conventions d'appel?alignas
mot-clé.std::aligned_storage
n'est pas vraiment nécessaire, étant donné que__m128
est déjà correctement alignés et que vous souhaitez plutôt un__m128
membre au lieu d'unstd::aligned_storage
membre. Mais bien sûr,alignas
est la nouvelle plate-forme indépendante de dire__declspec(align())
(ou quelle que soit la gcc aime), même si aucun d'entre eux sont généralement nécessaires à tous. Mais tout cela ne contribue que pour mémoire statique de l'alignement de toute façon.Fondamentalement, vous devez vous assurer que vos vecteurs sont correctement alignés parce que SIMD types de vecteurs, normalement, ont de plus grandes exigences alignement que tous les types intégrés.
Qui exige de faire les choses suivantes:
Assurez-vous que
Vector3
est alignée correctement quand il est sur la pile ou d'un membre d'une structure. Ceci est fait par l'application de__attribute__((aligned(32)))
àVector3
classe (ou quel que soit l'attribut est pris en charge par votre compilateur). Notez que vous n'avez pas besoin d'appliquer l'attribut de structures contenant desVector3
, qui n'est pas nécessaire et pas assez (c'est à dire pas besoin de l'appliquer àSphere
).Assurez-vous que
Vector3
ou son enveloppant la structure est correctement aligné lors de l'utilisation d'allocation de tas. Ceci est fait en utilisantposix_memalign()
(ou d'une fonction similaire pour votre plate-forme) au lieu d'utiliser de la plainemalloc()
ouoperator new()
parce que les deux derniers aligner la mémoire pour les types intégrés (normalement 8 ou 16 octets), qui n'est pas garanti d'être suffisant pour SIMD types.Le problème avec les opérateurs est que par eux-mêmes ils ne sont pas suffisants. Ils n'affectent pas la pile des allocations, pour lesquels vous avez encore besoin de
__declspec(align(16))
.__declspec(align(16))
affecte la façon dont le compilateur place les objets en mémoire, si et seulement si il a le choix. Pour les nouvelles ed des objets, le compilateur n'a aucun choix mais pour utiliser la mémoire renvoyée paroperator new
.Idéalement, utiliser un compilateur qui les gère en natif. Il n'y a pas de raison théorique pourquoi ils ont besoin d'être traités différemment des
double
. Autre chose, lisez la documentation du compilateur pour des solutions de contournement. Chaque handicap compilateur aura son propre ensemble de problèmes et donc son propre ensemble de solutions de contournement.__declspec(align(16))
est obsolète. Je suppose que la partie dépend du compilateur? Je ne suis pas sûr que je comprends la 3. une partie de votre réponse. Qu'entendez-vous par "gérer nativement'. J'utilise le compilateur fourni avec Visual Studio 2012 Express.__declspec(align(8))
pour ceux. Je n'ai pas de VS2012 donc je ne peux pas dire avec certitude si c'est que smart déjà.__declspec(align(16))
(ou quel que soit votre compilateur utilise) n'est pas obsolète, il n'est obsolète si un type a déjà un bien alignés, ce qui est le cas pourSphere
. Le truc, c'est juste que c'est la plupart du temps, obsolète pour votreVector3
classe, car il sera très probablement contenir un membre de type__m128
, qui est garanti pour être 16-alignés.operator new
. Mais je voudrais plutôt se spécialiserstd::allocator
au lieu de faire votre propre, parce qu'il n'y est pratiquement jamais le cas où vous avez besoin des non-alignés de la mémoire pour l'alignement de type et de cette façon vous n'avez pas de soins pour l'utilisation de la bonne allocateur chaque fois que vous instanciez unstd::vector
.