Si je veux traiter des données dans un std::vector
avec SSE, j'ai besoin d'un alignement de 16 octets. Comment puis-je y arriver? Dois-je écrire mon propre allocateur? Ou est-ce que l'allocateur par défaut s'aligne déjà sur les limites de 16 octets?
La norme C++ requiert des fonctions d'allocation (malloc()
et operator new()
) pour allouer de la mémoire correctement alignée pour tout type standard. Comme ces fonctions ne reçoivent pas l'exigence d'alignement en tant qu'argument, cela signifie en pratique que l'alignement pour toutes les allocations est le même et qu'il s'agit de l'alignement d'un type standard avec l'exigence d'alignement la plus grande, qui est souvent long double
et/ou long long
( voir boost max_align union ).
Les instructions vectorielles, telles que SSE et AVX, ont des exigences d’alignement plus strictes (alignées sur 16 octets pour un accès 128 bits et sur un octet alignées sur 32 octets) que celles fournies par les fonctions d’allocation standard C++. posix_memalign()
ou memalign()
peut être utilisé pour satisfaire de telles attributions avec des exigences d'alignement plus strictes.
Vous devez utiliser un allocateur personnalisé avec des conteneurs std::
, tels que vector
. Je ne me souviens plus qui a écrit le suivant, mais je l'utilise depuis un certain temps et cela semble fonctionner (vous devrez peut-être changer _aligned_malloc
en _mm_malloc
, en fonction du compilateur/de la plate-forme):
#ifndef ALIGNMENT_ALLOCATOR_H
#define ALIGNMENT_ALLOCATOR_H
#include <stdlib.h>
#include <malloc.h>
template <typename T, std::size_t N = 16>
class AlignmentAllocator {
public:
typedef T value_type;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T * pointer;
typedef const T * const_pointer;
typedef T & reference;
typedef const T & const_reference;
public:
inline AlignmentAllocator () throw () { }
template <typename T2>
inline AlignmentAllocator (const AlignmentAllocator<T2, N> &) throw () { }
inline ~AlignmentAllocator () throw () { }
inline pointer adress (reference r) {
return &r;
}
inline const_pointer adress (const_reference r) const {
return &r;
}
inline pointer allocate (size_type n) {
return (pointer)_aligned_malloc(n*sizeof(value_type), N);
}
inline void deallocate (pointer p, size_type) {
_aligned_free (p);
}
inline void construct (pointer p, const value_type & wert) {
new (p) value_type (wert);
}
inline void destroy (pointer p) {
p->~value_type ();
}
inline size_type max_size () const throw () {
return size_type (-1) / sizeof (value_type);
}
template <typename T2>
struct rebind {
typedef AlignmentAllocator<T2, N> other;
};
bool operator!=(const AlignmentAllocator<T,N>& other) const {
return !(*this == other);
}
// Returns true if and only if storage allocated from *this
// can be deallocated from other, and vice versa.
// Always returns true for stateless allocators.
bool operator==(const AlignmentAllocator<T,N>& other) const {
return true;
}
};
#endif
Utilisez-le comme ceci (changez le 16 en un autre alignement, si nécessaire):
std::vector<T, AlignmentAllocator<T, 16> > bla;
Cela garantit toutefois que le bloc de mémoire que std::vector
utilise est aligné sur 16 octets. Si sizeof(T)
n'est pas un multiple de 16, certains de vos éléments ne seront pas alignés. Selon votre type de données, cela peut ne pas être un problème. Si T
est int
(4 octets), ne chargez que les éléments dont l'index est un multiple de 4. S'il s'agit de double
(8 octets), uniquement des multiples de 2, etc.
Le vrai problème est que si vous utilisez des classes en tant que T
, dans ce cas, vous devrez spécifier vos exigences d'alignement dans la classe elle-même (là encore, selon le compilateur, cela pourrait être différent; l'exemple concerne GCC):
class __attribute__ ((aligned (16))) Foo {
__attribute__ ((aligned (16))) double u[2];
};
Nous avons presque fini! Si vous utilisez Visual C++ (au moins, version 2010), vous ne pourrez pas utiliser un std::vector
avec les classes dont vous avez spécifié l'alignement, à cause de std::vector::resize
.
Lors de la compilation, si vous obtenez l'erreur suivante:
C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector(870):
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned
Vous devrez pirater votre fichier stl::vector header
:
vector
[C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector]void resize( _Ty _Val )
[ligne 870 sur VC2010]void resize( const _Ty& _Val )
.Au lieu d'écrire votre propre allocateur, comme suggéré avant , vous pouvez utiliser boost::alignment::aligned_allocator
pour std::vector
comme ceci:
#include <vector>
#include <boost/align/aligned_allocator.hpp>
template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
Si sizeof(T)*vector.size() > 16
alors Oui.
En supposant que votre vecteur utilise des allocateurs normaux
Avertissement: tant que alignof(std::max_align_t) >= 16
est le même que l'alignement maximal.
Mise à jour 25/2017/2017 nouvelle norme n4659
S'il est aligné pour tout ce qui est supérieur à 16, il l'est aussi correctement pour 16.
Les alignements sont représentés sous forme de valeurs du type std :: size_t. Les alignements valides incluent uniquement les valeurs renvoyées par une expression alignof pour les types fondamentaux, ainsi qu'un ensemble supplémentaire de valeurs définies par la mise en œuvre, qui peuvent être vides. Chaque valeur d'alignement doit être une puissance intégrale non négative de deux.
Les alignements ont un ordre allant d'alignements plus faibles à plus forts ou plus stricts. Les alignements plus stricts ont des valeurs d’alignement plus grandes. Une adresse qui satisfait à une exigence d'alignement satisfait également à toute exigence d'alignement valide plus faible.
new et new [] renvoient des valeurs alignées de sorte que les objets soient correctement alignés pour leur taille:
[Remarque: lorsque la fonction d'attribution renvoie une valeur autre que null, il doit s'agir d'un pointeur sur un bloc de stockage dans lequel l'espace pour l'objet a été réservé. Le bloc de stockage est supposé être correctement aligné et de la taille demandée. L'adresse de l'objet créé ne sera pas nécessairement la même que celle du bloc si l'objet est un tableau. - note de fin]
Notez que la plupart des systèmes ont un alignement maximum. La mémoire allouée dynamiquement n'a pas besoin d'être alignée sur une valeur supérieure à celle-ci.
Un alignement fondamental est représenté par un alignement inférieur ou égal au plus grand alignement pris en charge par l'implémentation dans tous les contextes, ce qui est égal à alignof (std :: max_align_t) (21.2). L'alignement requis pour un type peut être différent lorsqu'il est utilisé en tant que type d'objet complet et lorsqu'il est utilisé en tant que le type d'un sous-objet.
Ainsi, tant que votre mémoire de vecteurs allouée est supérieure à 16 octets, elle sera correctement alignée sur des limites de 16 octets.
Ecrivez votre propre allocateur. allocate
et deallocate
sont les plus importants. Voici un exemple:
pointer allocate( size_type size, const void * pBuff = 0 )
{
char * p;
int difference;
if( size > ( INT_MAX - 16 ) )
return NULL;
p = (char*)malloc( size + 16 );
if( !p )
return NULL;
difference = ( (-(int)p - 1 ) & 15 ) + 1;
p += difference;
p[ -1 ] = (char)difference;
return (T*)p;
}
void deallocate( pointer p, size_type num )
{
char * pBuffer = (char*)p;
free( (void*)(((char*)p) - pBuffer[ -1 ] ) );
}
Utilisez declspec(align(x,y))
comme expliqué dans le tutoriel de vectorisation pour Intel, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf