Определение максимально возможного выравнивания в C++

Есть ли какой-нибудь переносимый способ определить максимально возможное выравнивание для любого типа?

Например, в x86 инструкции SSE требуют выравнивания по 16 байтам, но, насколько мне известно, никакие инструкции не требуют большего, поэтому любой тип можно безопасно хранить в буфере с выравниванием по 16 байтам.

Мне нужно создать буфер (например, массив символов), куда я могу записывать объекты произвольных типов, и поэтому мне нужно иметь возможность полагаться на начало буфера для выравнивания.

Если ничего не помогает, я знаю, что выделение массива символов с помощью new гарантирует максимальное выравнивание, но с шаблонами TR1/C++0x alignment_of и aligned_storage мне интересно, можно ли создать буфер на месте в моем классе буфера, вместо того, чтобы требовать дополнительной косвенности указателя динамически выделяемого массива.

Идеи?

Я понимаю, что существует множество вариантов определения максимального выравнивания для ограниченного набора типов: объединение или просто alignment_of из TR1, но моя проблема в том, что набор типов неограничен. Я не знаю заранее, какие объекты должны храниться в буфере.


person jalf    schedule 06.10.2009    source источник
comment
портативный в каком отношении, точно? каждому компилятору? для каждой ОС? для каждой архитектуры?   -  person San Jacinto    schedule 06.10.2009
comment
Просто переносимый, как гарантируется стандартом C++ для работы. Конечно, я мог бы легко положиться на свои собственные знания целевой архитектуры и жестко запрограммировать максимальное выравнивание, но было бы неплохо, если бы сам язык предоставлял инструменты для ответа на этот вопрос.   -  person jalf    schedule 07.10.2009
comment
Обратите внимание, что параметр шаблона Align для std::aligned_storage<Len, Align> имеет аргумент по умолчанию default-alignment, который определяется как Значение default-alignment должно быть самым строгим требованием выравнивания для любого типа объекта C++, размер которого не превышает Len. Я не знаю, считаются ли типы SSE типами объектов C++, а в стандартной библиотеке VC10 нет аргумента по умолчанию, поэтому я не знаю, каково предполагаемое значение (у меня нет других реализаций стандартной библиотеки на эта машина).   -  person James McNellis    schedule 30.12.2010
comment
@James: Ого, это должно решить проблему. Хорошая находка. Я только что протестировал его с помощью GCC, и, кажется, по умолчанию он равен 16 на x86, что позволяет использовать типы SSE, а также любые другие. Если вы сделаете это ответом, я приму это. :)   -  person jalf    schedule 30.12.2010
comment
Лучше на 14 месяцев позже, чем никогда, а? ;-)   -  person James McNellis    schedule 30.12.2010
comment
Новые наборы инструкций требуют большего выравнивания — AVX/AVX2 требует выравнивания по 32 байта, а AVX512 — выравнивания по 64 байта.   -  person Daniel Frużyński    schedule 29.06.2020


Ответы (6)


В C++0x параметр шаблона Align для std::aligned_storage<Len, Align> имеет аргумент по умолчанию «выравнивание по умолчанию», который определяется как (N3225 §20.7.6.6, таблица 56):

Значение default-alignment должно быть самым строгим требованием выравнивания для любого типа объекта C++, размер которого не превышает Len.

Неясно, будут ли типы SSE считаться «типами объектов C++».

Аргумент по умолчанию не был частью TR1 aligned_storage; он был добавлен для C++0x.

person James McNellis    schedule 30.12.2010

В C++11 std::max_align_t, определенный в заголовке cstddef, представляет собой тип POD, требования к выравниванию которого не менее строгие (такие же большие), как и у любого скалярного типа.

Используя новый оператор alignof, это будет так же просто, как alignof(std::max_align_t)

person Ricky65    schedule 17.04.2013
comment
для компиляторов, которые не поддерживают alignof (например, MSVC11), вы можете использовать std::alignment_of‹std::max_align_t›::value - person Valentin Milea; 28.06.2013
comment
Это прекрасно работает до тех пор, пока в стандартной библиотеке не появится ошибка :-/ - person Cameron; 17.12.2016
comment
В любом случае структуры не являются скалярными типами. Возможно, new для правильного выравнивания нескалярных значений использует более строгое выравнивание, чем std::max_align_t. - person Maxpm; 26.03.2019

К сожалению, обеспечение максимального выравнивания намного сложнее, чем должно быть, и нет никаких гарантированных решений, насколько я знаю. Из блога GotW (Статья Fast 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];
};

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

Это самый близкий «взлом», который я знаю для этого.

Есть еще один подход, который я лично использовал для сверхбыстрого распределения. Обратите внимание, что это зло, но я работаю в области трассировки лучей, где скорость является одним из самых важных показателей качества, и мы ежедневно профилируем код. Он включает в себя использование распределителя кучи с предварительно выделенной памятью, который работает как локальный стек (просто увеличивает указатель при выделении и уменьшает его при освобождении).

Я использую его, в частности, для Pimpls. Однако просто иметь распределитель недостаточно; чтобы такой аллокатор работал, мы должны предположить, что память для класса Foo выделяется в конструкторе, та же самая память освобождается только в деструкторе, а сам Foo создается в стеке. Чтобы сделать это безопасным, мне нужна функция, чтобы увидеть, находится ли указатель this класса в локальном стеке, чтобы определить, можем ли мы использовать наш сверхбыстрый распределитель стека на основе кучи. Для этого нам пришлось исследовать решения для конкретной ОС: я использовал TIB и TEB для Win32/Win64, и мои коллеги нашли решения для Linux и Mac OS X.

В результате, после недели изучения специфичных для ОС методов для определения диапазона стека, требований к выравниванию, а также большого количества тестов и профилирования, стал распределитель, который мог выделять память за 4 такта в соответствии с нашими тестами счетчика тиков, в отличие от примерно 400 циклов для malloc/operator new (наш тест включал конфликт потоков, поэтому malloc, вероятно, будет немного быстрее, чем это в однопоточных случаях, возможно, на пару сотен циклов). Мы добавили стек кучи для каждого потока и определили, какой поток используется, что увеличило время примерно до 12 циклов, хотя клиент может отслеживать распределитель потоков, чтобы получить выделение 4 циклов. Он стер с карты точки доступа, основанные на распределении памяти.

Хотя вам не нужно преодолевать все эти проблемы, написание быстрого распределителя может быть проще и более применимо (например, позволяет определять объем памяти для выделения/освобождения во время выполнения), чем что-то вроде max_align здесь. max_align достаточно прост в использовании, но если вам нужна скорость для выделения памяти (и если вы уже профилировали свой код и нашли горячие точки в malloc/free/operator new/delete с основными вкладчиками, находящимися в коде, который вы контролируете) , написание собственного распределителя действительно может иметь значение.

person stinky472    schedule 27.06.2010
comment
+1. Вау, в 100 раз быстрее распределение. Спасибо, что поделились этой информацией. - person Peter Mortensen; 27.06.2010
comment
Хм, это очень интересно. Но на самом деле я не спрашивал о быстром распределении (в моем случае это на самом деле довольно простая проблема, потому что мне не нужно обрабатывать неприятные общие случаи, как вы, - и я должен сказать, я впечатлен у тебя получилось). Но мой вопрос был просто о том, чтобы объекты размещались по правильно выровненным адресам. - person jalf; 21.03.2012
comment
@jalf Ах, извините, обычно я думаю, что когда кто-то начинает заниматься проблемами выравнивания и хранить различные типы данных в одном буфере, это часто имеет в виду распределитель памяти и производительность. Боюсь, я не знаю никакого портативного способа обеспечить максимальное выравнивание для данного типа. Обычно в таких случаях мне приходилось ориентироваться на платформу. Чтобы попытаться сделать это безопасным, я часто использовал подходы opt-in (тип, выравнивание которого неизвестно, будет выравниваться по границам двойного квадрослова, максимально возможное выравнивание, о котором я знаю). - person stinky472; 22.03.2012
comment
@jalf ... таким образом, я должен получить специфичную для платформы (хотелось бы иметь возможность избежать этого), но я достаточно уверен в том, что двойное выравнивание четырех слов, хотя и потенциально расточительное, не вызовет проблем, поскольку я использую его по умолчанию для неизвестного типа T, выравнивание которого не зарегистрировано. - person stinky472; 22.03.2012

За исключением какого-то типа maximally_aligned_t, который все компиляторы добросовестно обещали поддерживать для всех архитектур повсюду, я не понимаю, как это можно решить во время компиляции. Как вы говорите, набор потенциальных типов неограничен. Действительно ли дополнительная косвенность указателя так важна?

person David Seiler    schedule 06.10.2009
comment
Это может быть не так, но мне любопытно, есть ли решение. C++0x добавляет несколько других функций, связанных с выравниванием, и реализация уже должна определять максимально возможное выравнивание в других случаях (при динамическом выделении массива символов), поэтому я подумал, что может быть какой-то неясный шаблон стандартной библиотеки, который предоставляет это ценность. - person jalf; 07.10.2009
comment
Ага. Это интересный вопрос, и я бы хотел, чтобы у меня был лучший ответ для вас, но я не думаю, что есть какой-либо способ, соответствующий стандартам. Тем не менее, maximally_aligned_t (или лучше, maximal_alignment) было бы нетрудно реализовать; возможно, вам следует предложить это для С++ 1x :) - person David Seiler; 07.10.2009
comment
Как упоминается в других ответах, начиная с C11 существует такой тип: en.cppreference.com/ w/c/types/max_align_t См. также en.cppreference.com/w /c/память/aligned_alloc - person Max Barraclough; 18.08.2020

Выделение выровненной памяти сложнее, чем кажется — см., например, Реализация выровненного распределения памяти

person Martin Beckett    schedule 06.10.2009
comment
Я знаю, что это сложно. Это был не мой вопрос. ;) Но стандарт дает некоторые гарантии, и особенно если принять во внимание C++0x, у вас есть парочка стандартных инструментов, которые помогут. - person jalf; 07.10.2009
comment
Хитрость не относится к Джальфу, потому что он не делает общий распределитель. Все, что ему нужно, это иметь дополнительное место в своем буфере и округлить указатель в буфере до следующего желаемого блока выравнивания. - person Potatoswatter; 28.12.2009

Это то, что я использую. В дополнение к этому, если вы выделяете память, массив символов new() с длиной, большей или равной 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
            };
        }
person James Taylor    schedule 20.03.2012
comment
К сожалению, это не обеспечивает подходящего выравнивания для типов SSE. - person jalf; 21.03.2012