Détermination de l’alignement maximal possible en C ++

Existe-t-il un moyen portable de déterminer le meilleur alignement possible pour tout type?

Par exemple, sur x86, les instructions SSE nécessitent un alignement sur 16 octets, mais autant que je sache, aucune instruction ne requirejs plus que cela, de sorte que tout type peut être stocké en toute sécurité dans un tampon aligné sur 16 octets.

Je dois créer un tampon (tel qu’un tableau de caractères) où je puisse écrire des objects de types arbitraires. Je dois donc pouvoir compter sur le début du tampon pour l’aligner.

Si tout le rest échoue, je sais qu’il est certain que l’allocation d’un tableau de caractères à new a un alignement maximum, mais avec les modèles TR1 / C ++ 0x alignment_of et aligned_storage , je me demande s’il serait possible de créer le tampon sur place. dans ma classe de mémoire tampon, plutôt que de requérir l’indirection supplémentaire du pointeur d’un tableau alloué dynamicment.

Des idées?

Je me rends compte qu’il existe de nombreuses options pour déterminer l’alignement maximal pour un ensemble limité de types: une union, ou tout simplement alignment_of de TR1, mais le problème est que l’ensemble des types est non limité. Je ne sais pas à l’avance quels objects doivent être stockés dans la mémoire tampon.

Dans C ++ 0x, le paramètre de modèle Align de std::aligned_storage a un argument par défaut de “default-alignement”, défini comme suit (N3225 §20.7.6.6, tableau 56):

La valeur default-alignement doit être l’exigence d’alignement la plus ssortingcte pour tout type d’object C ++ dont la taille n’est pas supérieure à Len .

Il n’est pas clair si les types SSE seraient considérés comme des “types d’object C ++”.

L’argument par défaut ne faisait pas partie du TR1 aligned_storage ; il a été ajouté pour C ++ 0x.

Dans C ++ 11, std :: max_align_t est défini dans l’entête. Cstddef est un type de POD dont les exigences d’alignement sont au moins aussi ssortingctes (aussi grandes) que celles de chaque type de scalaire.

En utilisant le nouvel opérateur alignof, ce serait aussi simple que alignof(std::max_align_t)

À part un type maximally_aligned_t que tous les compilateurs ont promis de prendre en charge de manière fidèle pour toutes les architectures, je ne vois pas comment cela pourrait être résolu au moment de la compilation. Comme vous le dites, l’ensemble des types potentiels est illimité. Le pointage supplémentaire indirectionnel est-il vraiment un gros problème?

Malheureusement, il est beaucoup plus difficile d’assurer l’alignement maximum que possible, et il n’existe aucune solution garantie AFAIK. Extrait du blog GotW ( article de Fast Pimpl ):

 union max_align { short dummy0; long dummy1; double dummy2; long double dummy3; void* dummy4; /*...and pointers to functions, pointers to member functions, pointers to member data, pointers to classes, eye of newt, ...*/ }; union { max_align m; char x_[sizeofx]; }; 

Cela n’est pas garanti d’être entièrement portable, mais dans la pratique, il est suffisamment proche, car il existe peu ou pas de systèmes sur lesquels cela ne fonctionnera pas comme prévu.

C’est à peu près le “hack” le plus proche que je connaisse pour ça.

Il y a une autre approche que j’ai personnellement utilisée pour une allocation ultra rapide. Notez que c’est mauvais, mais je travaille dans des champs de lancer de rayons où la vitesse est l’une des plus grandes mesures de la qualité et nous profilons le code quotidiennement. Cela implique l’utilisation d’un allocateur de tas avec une mémoire préallouée qui fonctionne comme la stack locale (incrémente simplement un pointeur sur l’allocation et le décrémente sur la désallocation).

Je l’utilise surtout pour Pimpls . Cependant, il ne suffit pas d’avoir l’allocateur. pour qu’un tel allocateur fonctionne, nous devons supposer que la mémoire d’une classe, Foo, est allouée dans un constructeur, la même mémoire est également désallouée uniquement dans le destructeur et que Foo lui-même est créé sur la stack. Pour le rendre sûr, j’avais besoin d’une fonction pour voir si le pointeur ‘this’ d’une classe est sur la stack locale afin de déterminer si nous pouvons utiliser notre allocateur de stack super rapide basé sur le tas. Pour cela, nous avons dû rechercher des solutions spécifiques au système d’exploitation: j’ai utilisé des TIB et des TEB pour Win32 / Win64, et mes collègues ont trouvé des solutions pour Linux et Mac OS X.

Le résultat, après une semaine de recherche sur les méthodes spécifiques au système d’exploitation pour détecter la plage de stacks, les exigences d’alignement et de nombreux tests et profilages, a été un allocateur capable d’allouer de la mémoire sur 4 cycles d’horloge en fonction de nos repères de compteur de ticks, par opposition à environ 400 cycles pour malloc / opérateur nouveau (notre test implique des conflits de threads, donc malloc est susceptible d’être un peu plus rapide que cela dans les cas mono-threadés, peut-être quelques centaines de cycles). Nous avons ajouté une stack de tas par thread et avons détecté le thread utilisé, ce qui a augmenté le temps à environ 12 cycles, bien que le client puisse garder une trace de l’allocateur de threads pour obtenir les allocations de 4 cycles. Il a effacé les points chauds basés sur l’allocation de mémoire de la carte.

Bien que vous n’ayez pas à passer par tous ces problèmes, écrire un allocateur rapide pourrait être plus facile et plus généralement applicable (ex: autoriser la quantité de mémoire à allouer / désallouer à déterminer à l’exécution) à quelque chose comme max_align ici. max_align est assez facile à utiliser, mais si vous avez max_align allouer de la mémoire (et en supposant que vous avez déjà profilé votre code et trouvé des points chauds dans malloc / free / operator new / delete avec des consortingbuteurs majeurs dans le code, vous avez le contrôle). , écrire votre propre allocateur peut vraiment faire la différence.

L’allocation de mémoire alignée est plus délicate qu’il n’y paraît – voir par exemple Implémentation de l’allocation de mémoire alignée

C’est ce que j’utilise. De plus, si vous allouez de la mémoire, un tableau new () ‘d de longueur égale ou supérieure à max_alignment sera aligné sur max_alignment afin que vous puissiez ensuite utiliser des index dans ce tableau pour obtenir des adresses alignées.

 enum { max_alignment = boost::mpl::deref< boost::mpl::max_element< boost::mpl::vector< boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type, boost::mpl::int_::value>::type >::type >::type >::type::value }; }