Как выровнены векторные данные?

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

9 ответов

Решение

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

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

Вы должны использовать пользовательский распределитель с 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 ),

Вместо того, чтобы писать свой собственный распределитель, как предлагалось ранее, вы можете использовать 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>>;

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

Если 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 байтов.

Напишите свой собственный распределитель. 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 ] ) );
}

Современный ответ на устаревший (но важный) вопрос.

Написание собственного Allocatorкласс [шаблон] сразу приходит на ум, как говорят другие. Начиная с С++ 11 и до С++ 17 реализация будет в основном ограничена (по стандарту) использованием alignasи размещение new. C++17 поднимает C11 aligned_allocчто удобно. Кроме того, C++17 std::pmrпространство имен (заголовок <memory_resource>) представляет polymorphic_allocatorшаблон класса и memory_resourceабстрактный интерфейс для полиморфных распределений, сильно вдохновленный Boost. Было показано, что помимо возможности использования действительно универсального динамического кода, в некоторых случаях они обеспечивают повышение скорости; в этом случае ваш SIMD-код будет работать еще лучше.

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

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

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

Другие вопросы по тегам