Как выровнены векторные данные? - PullRequest
36 голосов
/ 10 декабря 2011

Если я хочу обработать данные в std::vector с SSE, мне нужно 16-байтовое выравнивание. Как я могу этого достичь? Нужно ли мне писать свой собственный распределитель? Или распределитель по умолчанию уже выровнен по 16-байтовым границам?

Ответы [ 8 ]

25 голосов
/ 17 декабря 2011

Вы должны использовать пользовательский распределитель с std:: контейнерами, такими как vector. Не могу вспомнить, кто написал следующий, но я использовал его в течение некоторого времени, и он, кажется, работает (возможно, вам придется изменить _aligned_malloc на _mm_malloc, в зависимости от компилятора / платформы):

#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

Используйте это следующим образом (при необходимости измените 16 на другое выравнивание):

std::vector<T, AlignmentAllocator<T, 16> > bla;

Это, однако, только гарантирует, что используемый блок памяти std::vector выровнен на 16 байтов. Если sizeof(T) не кратно 16, некоторые ваши элементы не будут выровнены. В зависимости от вашего типа данных, это может быть не проблема. Если T равно int (4 байта), загружаются только элементы, индекс которых кратен 4. Если это double (8 байтов), только кратны 2 и т. Д.

Реальная проблема в том, что если вы используете классы как T, в этом случае вам придется указать свои требования выравнивания в самом классе (опять же, в зависимости от компилятора, это может отличаться; пример для GCC):

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

Мы почти закончили! Если вы используете Visual C ++ (по крайней мере, версия 2010), вы не сможете использовать std::vector с классами, выравнивание которых вы указали, из-за std::vector::resize.

При компиляции появляется следующая ошибка:

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

Вам придется взломать ваш stl::vector header файл:

  1. Найдите заголовочный файл vector [C: \ Program Files \ Microsoft Visual Studio 10.0 \ VC \ include \ vector]
  2. Найдите метод void resize( _Ty _Val ) [строка 870 в VC2010]
  3. Измените его на void resize( const _Ty& _Val ).
19 голосов
/ 10 декабря 2011
Стандарт

C ++ требует, чтобы функции выделения (malloc() и operator new()) выделяли память, соответствующим образом выровненную для любого типа standard . Поскольку эти функции не получают требование выравнивания в качестве аргумента, на практике это означает, что выравнивание для всех распределений одинаково и является выравниванием стандартного типа с наибольшим требованием выравнивания, которое часто составляет long double и / или long long (см. boost max_align union ).

Векторные инструкции, такие как SSE и AVX, имеют более строгие требования выравнивания (16-байтовое выравнивание для 128-битного доступа и 32-байтовое выравнивание для 256-битного доступа), чем те, которые предусмотрены стандартными функциями выделения C ++. posix_memalign() или memalign() могут использоваться для удовлетворения таких распределений с более строгими требованиями выравнивания.

9 голосов
/ 23 августа 2017

Вместо того, чтобы писать свой собственный распределитель, как предложенный ранее , вы можете использовать boost::alignment::aligned_allocator для std::vector, например:

#include <vector>
#include <boost/align/aligned_allocator.hpp>

template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
3 голосов
/ 10 декабря 2011

Краткий ответ:

Если sizeof(T)*vector.size() > 16, то Да.
Предполагая, что ваш вектор использует нормальные распределители

Предостережение: пока alignof(std::max_align_t) >= 16, поскольку это максимальное выравнивание.

Длинный ответ:

Обновлен новый стандарт 25 августа / 2017 года n4659

Если он выровнен для всего, что больше 16, он также выровнен правильно для 16.

6.11 Выравнивание (пункт 4/5)

Выравнивания представлены в виде значений типа std :: size_t. Допустимые выравнивания включают только те значения, которые возвращаются выражением alignof для основных типов, плюс дополнительный набор значений, определенный реализацией, который может быть пустым. Каждое значение выравнивания должно быть неотрицательной интегральной степенью двойки.

Выравнивания имеют порядок от более слабого к более сильному или более строгому выравниванию. Более строгие выравнивания имеют большие значения выравнивания. Адрес, который удовлетворяет требованию выравнивания, также удовлетворяет любому более слабому действительному требованию выравнивания.

new и new [] возвращают значения, которые выровнены так, что объекты правильно выровнены по их размеру:

8.3.4 Новый (пункт 17)

[Примечание: когда функция выделения возвращает значение, отличное от нуля, это должен быть указатель на блок хранения, в котором зарезервировано пространство для объекта. Предполагается, что блок хранения выровнен соответствующим образом и имеет запрошенный размер. Адрес созданного объекта не обязательно будет таким же, как адрес блока, если объект является массивом. - конец примечания]

Обратите внимание, что большинство систем имеют максимальное выравнивание. Динамически распределенная память не должна быть выровнена по значению, большему, чем это.

6.11 Выравнивание (параграф 2)

Фундаментальное выравнивание представлено выравниванием, меньшим или равным наибольшему поддерживаемому выравниванию реализацией во всех контекстах, что равно alignof (std :: max_align_t) (21.2). Выравнивание требуемый для типа может отличаться, когда он используется в качестве типа законченного объекта и когда он используется как тип подобъекта.

Таким образом, если ваша выделенная векторная память больше 16 байт, она будет правильно выровнена по границам 16 байт.

3 голосов
/ 10 декабря 2011

Напишите свой собственный распределитель.allocate и deallocate являются важными.Вот один пример:

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 ] ) );
}
0 голосов
/ 13 июля 2015

Используйте declspec(align(x,y)), как описано в руководстве по векторизации для Intel, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf

0 голосов
/ 10 декабря 2011

Стандарт требует, чтобы new и new[] возвращали данные, выровненные для любого типа данных , который должен включать SSE. Соблюдает ли MSVC это правило - другой вопрос.

0 голосов
/ 10 декабря 2011

Не думайте о контейнерах STL. Их интерфейс / поведение определяется, но не то, что стоит за ними. Если вам нужен необработанный доступ, вам нужно написать собственную реализацию, соответствующую правилам, которые вы хотели бы иметь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...