Определение максимально возможного выравнивания в С++
Есть ли какой-либо переносной способ определения того, что максимально возможное выравнивание для любого типа?
Например, для x86 для инструкций SSE требуется 16-байтовое выравнивание, но, насколько мне известно, инструкции не требуют большего, поэтому любой тип можно безопасно хранить в буфере с выравниванием по 16 байт.
Мне нужно создать буфер (например, массив char), где я могу писать объекты произвольных типов, поэтому мне нужно иметь возможность полагаться на начало выравниваемого буфера.
Если все остальное не удается, я знаю, что при назначении массива char с new
гарантировано максимальное выравнивание, но с шаблонами TR1/С++ 0x alignment_of
и aligned_storage
, мне интересно, было бы возможно создать буфер на месте в моем классе буфера, вместо того, чтобы требовать дополнительной указательной ссылки на динамически выделенный массив.
Идеи?
Я понимаю, что существует множество опций для определения максимального выравнивания для ограниченного набора типов: объединение или просто alignment_of
из TR1, но моя проблема заключается в том, что набор типов неограничен. Я заранее не знаю, какие объекты должны храниться в буфере.
Ответы
Ответ 1
В С++ 0x параметр шаблона Align
std::aligned_storage<Len, Align>
имеет аргумент по умолчанию "выравнивание по умолчанию", который определяется как (N3225 §20.7.6.6, таблица 56):
Значение выравнивания по умолчанию должно быть самым строгим требованием выравнивания для любого типа объекта С++, размер которого не превышает Len
.
Неясно, будут ли типы SSE рассматриваться как типы объектов С++.
Аргумент по умолчанию не был частью TR1 aligned_storage
; он был добавлен для С++ 0x.
Ответ 2
В С++ 11 std:: max_align_t, определенный в заголовке cstddef, является типом POD, требование выравнивания которого, по крайней мере, столь же строго (как большой), как и для каждого скалярного типа.
Используя новый оператор alignof, он будет таким же простым, как alignof(std::max_align_t)
Ответ 3
За исключением некоторого типа maximally_aligned_t
, который все компиляторы пообещали добросовестно поддерживать для всех архитектур везде, я не вижу, как это можно было бы решить во время компиляции. Как вы говорите, набор потенциальных типов неограничен. Является ли дополнительная ссылка указателем действительно той большой сделкой?
Ответ 4
К сожалению, максимальное согласование намного сложнее, чем должно быть, и нет гарантированных решений AFAIK. Из GotW blog (статья быстрого Pimpl):
union max_align {
short dummy0;
long dummy1;
double dummy2;
long double dummy3;
void* dummy4;
/*...and pointers to functions, pointers to
member functions, pointers to member data,
pointers to classes, eye of newt, ...*/
};
union {
max_align m;
char x_[sizeofx];
};
Это не гарантируется полностью портативный, но на практике он близок достаточно, потому что их мало или нет системы, на которых это не будет работать ожидается.
Что касается ближайшего "взлома", я знаю для этого.
Существует еще один подход, который я использовал лично для сверхбыстрого распределения. Обратите внимание, что это зло, но я работаю в областях raytracing, где скорость является одним из самых высоких показателей качества, и мы ежедневно просматриваем код. Он включает использование распределителя кучи с предварительно выделенной памятью, которая работает как локальный стек (просто увеличивает указатель на распределение и уменьшает один при освобождении).
Я использую его для Pimpls. Однако, просто наличие распределителя недостаточно; для того, чтобы такой распределитель работал, мы должны предположить, что память для класса Foo выделяется в конструкторе, та же память также освобождается только в деструкторе, а сам Foo создается в стеке. Чтобы это было безопасно, мне нужна была функция, чтобы увидеть, есть ли указатель класса 'this' класса в локальном стеке, чтобы определить, можем ли мы использовать наш супер быстрый кучевой распределитель стека. Для этого нам пришлось исследовать решения, специфичные для ОС: я использовал TIBs и TEBs для Win32/Win64, а мои сотрудники нашли решения для Linux и Mac OS X.
В результате, после недели исследования методов, специфичных для ОС, для определения диапазона стеков, требований к выравниванию и большого количества тестирования и профилирования, был распределитель, который мог бы распределять память в 4 тактовых циклах в соответствии с нашими критериями контроля счетчиков против около 400 циклов для malloc/operator new (наш тест связан с конфликтом потоков, поэтому malloc скорее всего будет немного быстрее, чем это в однопоточных случаях, возможно, в течение нескольких сотен циклов). Мы добавили кучу кучи в потоке и обнаружили, какой поток использовался, что увеличило время до 12 циклов, хотя клиент может отслеживать распределитель потоков, чтобы получить 4 распределения циклов. Он уничтожил горячие точки распределения памяти на карте.
В то время как вам не нужно преодолевать все эти проблемы, писать быстрый распределитель может быть проще и более общеприменимым (например: разрешение объема выделения/освобождения памяти для определения во время выполнения), чем что-то вроде max_align
здесь. max_align
достаточно прост в использовании, но если вы используете скорость для распределения памяти (и предположим, что вы уже профилировали свой код и обнаружили горячие точки в malloc/free/operator new/delete с основными участниками, находящимися в коде, у вас есть контроль более), написание собственного распределителя может действительно иметь значение.
Ответ 5
Выделение выровненной памяти сложнее, чем выглядит - см., например, Реализация выделенного выделения памяти
Ответ 6
Это то, что я использую. В дополнение к этому, если вы распределяете память, тогда новый() 'd массив char с длиной, большей или равной max_alignment, будет выровнен с max_alignment, чтобы затем вы могли использовать индексы в этом массиве для выравнивания адресов.
enum {
max_alignment = boost::mpl::deref<
boost::mpl::max_element<
boost::mpl::vector<
boost::mpl::int_<boost::alignment_of<signed char>::value>::type,
boost::mpl::int_<boost::alignment_of<short int>::value>::type,
boost::mpl::int_<boost::alignment_of<int>::value>::type, boost::mpl::int_<boost::alignment_of<long int>::value>::type,
boost::mpl::int_<boost::alignment_of<float>::value>::type,
boost::mpl::int_<boost::alignment_of<double>::value>::type,
boost::mpl::int_<boost::alignment_of<long double>::value>::type,
boost::mpl::int_<boost::alignment_of<void*>::value>::type
>::type
>::type
>::type::value
};
}