Если я хочу обрабатывать данные в std::vector
с SSE, мне нужно 16-байтовое выравнивание. Как я могу это достичь? Нужно ли мне писать собственный распределитель? Или распределитель по умолчанию уже выравнивается до 16 байтовых границ?
Как выравниваются векторные данные?
Ответ 1
Стандарт С++ требует, чтобы функции распределения (malloc()
и operator new()
) выделяли соответствующую ячейку памяти для любого стандартного типа. Поскольку эти функции не получают требования к выравниванию в качестве аргумента, на практике это означает, что выравнивание для всех распределений является одинаковым и является выравниванием стандартного типа с наибольшим требованием выравнивания, которое часто равно long double
и/или long long
(см. увеличить max_align union).
Векторные инструкции, такие как SSE и AVX, имеют более высокие требования к выравниванию (16-байтовый выровненный для 128-битного доступа и 32-байтовый выровненный для 256-битного доступа), чем те, которые предоставляются стандартными функциями распределения С++. posix_memalign()
или memalign()
могут использоваться для удовлетворения таких распределений с более высокими требованиями к выравниванию.
Ответ 2
Вы должны использовать собственный распределитель с контейнерами 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 С++ (по крайней мере, версию 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
:
- Найдите заголовочный файл
vector
[C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector] - Найдите метод
void resize( _Ty _Val )
[строка 870 на VC2010] - Измените его на
void resize( const _Ty& _Val )
.
Ответ 3
Вместо того, чтобы писать собственный распределитель, как предлагаемый до, вы можете использовать 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>>;
Ответ 4
Напишите свой собственный распределитель. 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 ] ) );
}
Ответ 5
Краткий ответ:
Если sizeof(T)*vector.size() > 16
, то да.
Предполагая, что вектор использует обычные распределители
Предостережение: Пока alignof(std::max_align_t) >= 16
, поскольку это максимальное выравнивание.
Длинный ответ:
Обновлен 25/Aug/2017 новый стандарт n4659
Если он выровнен для чего-либо большего, чем 16, он также правильно выровнен на 16.
6.11 Выравнивание (абзац 4/5)
Выравнивания представлены как значения типа std:: size_t. Допустимые выравнивания включают только те значения, которые возвращают выражение alignof для основных типов, плюс дополнительный набор значений, определенный реализацией, который может быть пустым. Каждое значение выравнивания должно быть неотрицательной интегральной степенью двух.
Выравнивания имеют порядок от более слабых до более сильных или строгих выравниваний. Более строгие выравнивания имеют более высокие значения выравнивания. Адрес, который удовлетворяет требованию выравнивания, также удовлетворяет любому более слабому правильному требованию выравнивания.
new и new [] возвращают значения, которые выровнены так, чтобы объекты были правильно выровнены по их размеру:
8.3.4 Новое (параграф 17)
[Примечание: когда функция распределения возвращает значение, отличное от нуля, оно должно быть указателем на блок хранения, в котором зарезервировано пространство для объекта. Предполагается, что блок хранения будет соответствующим образом выровнен и запрошенного размера. Адрес созданного объекта не обязательно будет таким же, как адрес блока, если объект является массивом. - конечная нота]
Обратите внимание, что большинство систем имеют максимальное выравнивание. Динамически выделенную память не нужно выравнивать со значением, большим этого.
6.11 Выравнивание (параграф 2)
Фундаментальное выравнивание представлено выравниванием, меньшим или равным наибольшему выравниванию по реализации во всех контекстах, которая равна выравниванию (std:: max_align_t) (21.2). Выравнивание требуемый для типа, может быть другим, когда он используется как тип полного объекта и когда он используется как тип подобъекта.
Таким образом, пока выделенная вами векторная память будет больше 16 байт, она будет правильно выровнена на 16 байтовых границах.
Ответ 6
Не думайте о контейнерах STL. Их интерфейс/поведение определяется, но не то, что стоит за ними. Если вам нужен необработанный доступ, вам придется написать свою собственную реализацию, которая будет следовать правилам, которые вы хотели бы иметь.
Ответ 7
Используйте declspec(align(x,y))
, как описано в учебнике по векторизации для Intel, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf
Ответ 8
Стандарт обязывает, что new
и new[]
возвращают данные, выровненные для любого типа данных, которые должны включать SSE. Независимо от того, действительно ли MSVC следует этому правилу, это другой вопрос.