¿Cómo se alinean los datos de un vector?

Si quiero procesar datos en un std::vector con SSE, necesito una alineación de 16 bytes. ¿Cómo puedo lograr eso? ¿Debo escribir mi propio asignador? ¿O el asignador predeterminado ya se alinea con los límites de 16 bytes?

El estándar C ++ requiere funciones de asignación ( malloc() y operator new() ) para asignar memoria adecuadamente alineada para cualquier tipo estándar . Como estas funciones no reciben el requisito de alineación como argumento, en la práctica significa que la alineación para todas las asignaciones es la misma y es la alineación de un tipo estándar con el mayor requisito de alineación, que a menudo es long double y / o long long (ver boost max_align union ).

Las instrucciones vectoriales, como SSE y AVX, tienen requisitos de alineación más fuertes (16 bytes alineados para acceso de 128 bits y 32 bytes alineados para acceso de 256 bits) que los provistos por las funciones de asignación estándar de C ++. posix_memalign() o memalign() se pueden usar para satisfacer tales asignaciones con requisitos de alineación más fuertes.

Debe usar un asignador personalizado con std:: containers, como vector . No puedo recordar quién escribió el siguiente, pero lo utilicé durante un tiempo y parece que funciona (es posible que tenga que cambiar _aligned_malloc por _mm_malloc , según el comstackdor / plataforma):

 #ifndef ALIGNMENT_ALLOCATOR_H #define ALIGNMENT_ALLOCATOR_H #include  #include  template  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  inline AlignmentAllocator (const AlignmentAllocator &) 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  struct rebind { typedef AlignmentAllocator other; }; bool operator!=(const AlignmentAllocator& 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& other) const { return true; } }; #endif 

Úselo así (cambie el 16 a otra alineación, si es necesario):

 std::vector > bla; 

Esto, sin embargo, solo se asegura de que el bloque de memoria std::vector tenga una alineación de 16 bytes. Si sizeof(T) no es un múltiplo de 16, algunos de sus elementos no estarán alineados. Dependiendo de su tipo de datos, esto podría no ser un problema. Si T es int (4 bytes), solo cargue elementos cuyo índice sea múltiplo de 4. Si es double (8 bytes), solo múltiplos de 2, etc.

El verdadero problema es si usa clases como T , en cuyo caso deberá especificar sus requisitos de alineación en la clase misma (de nuevo, dependiendo del comstackdor, esto podría ser diferente; el ejemplo es para GCC):

 class __attribute__ ((aligned (16))) Foo { __attribute__ ((aligned (16))) double u[2]; }; 

¡Ya casi hemos terminado! Si usa Visual C ++ (al menos, versión 2010), no podrá usar un std::vector con clases cuya alineación haya especificado, debido a std::vector::resize .

Al comstackr, si obtiene el siguiente error:

 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 

Tendrás que hackear tu archivo stl::vector header :

  1. Busque el archivo de encabezado vector [C: \ Archivos de progtwig \ Microsoft Visual Studio 10.0 \ VC \ include \ vector]
  2. Busque el void resize( _Ty _Val ) [línea 870 en VC2010]
  3. void resize( const _Ty& _Val ) para void resize( const _Ty& _Val ) .

En lugar de escribir su propio asignador, como se sugirió anteriormente , puede usar boost::alignment::aligned_allocator para std::vector como este:

 #include  #include  template  using aligned_vector = std::vector>; 

Respuesta corta:

Si sizeof(T)*vector.size() > 16 entonces sí.
Asumiendo que el vector usa asignadores normales

Advertencia: siempre que alignof(std::max_align_t) >= 16 ya que esta es la alineación máxima.

Respuesta larga:

Actualizado el 25 / Ago / 2017 nuevo estándar n4659

Si está alineado para cualquier cosa que sea mayor que 16, también está alineado correctamente para 16.

6.11 Alineación (Párrafo 4/5)

Las alineaciones se representan como valores del tipo std :: size_t. Las alineaciones válidas incluyen solo aquellos valores devueltos por una expresión de alineación de los tipos fundamentales más un conjunto adicional de valores definidos por la implementación, que pueden estar vacíos. Cada valor de alineación debe ser una potencia integral no negativa de dos.

Las alineaciones tienen un orden de alineaciones más débiles a más fuertes o más estrictas. Las alineaciones más estrictas tienen valores de alineación más grandes. Una dirección que satisface un requisito de alineación también satisface cualquier requisito de alineación válido más débil.

valores de retorno nuevos y nuevos [] que están alineados para que los objetos estén alineados correctamente para su tamaño:

8.3.4 Nuevo (párrafo 17)

[Nota: cuando la función de asignación devuelve un valor distinto de nulo, debe ser un puntero a un bloque de almacenamiento en el que se ha reservado espacio para el objeto. Se supone que el bloque de almacenamiento está apropiadamente alineado y del tamaño solicitado. La dirección del objeto creado no será necesariamente la misma que la del bloque si el objeto es una matriz. – nota final]

Tenga en cuenta que la mayoría de los sistemas tienen una alineación máxima. La memoria asignada dinámicamente no necesita estar alineada a un valor mayor que este.

6.11 Alineación (párrafo 2)

Una alineación fundamental se representa mediante una alineación inferior o igual a la alineación más grande admitida por la implementación en todos los contextos, que es igual a alignof (std :: max_align_t) (21.2). La alineación requerida para un tipo puede ser diferente cuando se usa como el tipo de un objeto completo y cuando se usa como el tipo de un subobjeto.

Por lo tanto, siempre que su memoria vectorial asignada sea mayor que 16 bytes, se alineará correctamente en los límites de 16 bytes.

Escribe tu propio asignador. allocate y deallocate son los más importantes. Aquí hay un ejemplo:

 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 ] ) ); } 

No asums nada sobre los contenedores STL. Su interfaz / comportamiento está definido, pero no lo que está detrás de ellos. Si necesita acceso sin formato, tendrá que escribir su propia implementación que siga las reglas que le gustaría tener.

Use declspec(align(x,y)) como se explica en el tutorial de vectorización para Intel, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf

El Estándar exige que los datos de retorno new y new[] estén alineados para cualquier tipo de datos, lo que debería incluir SSE. Si MSVC realmente sigue esa regla es otra pregunta.