Действительно ли выравнивание имеет значение для производительности в C++11?

Действительно ли выравнивание имеет значение для производительности в C++11?

В книге Страуструпа есть совет упорядочивать элементы в структуре, начиная с наибольшего и заканчивая наименьшим. Но мне интересно, проводил ли кто-нибудь измерения, чтобы увидеть, имеет ли это какое-то значение, и стоит ли об этом думать при написании кода.


person user3111311    schedule 28.12.2013    source источник
comment
Но мне интересно, если кто-то сделал измерения, чтобы действительно увидеть, имеет ли это какое-то значение, - вы могли бы сделать это и отчитаться....   -  person Mitch Wheat    schedule 28.12.2013
comment
@MitchWheat Если бы я знал, как, я бы не спрашивал.   -  person user3111311    schedule 28.12.2013
comment
вы пишете некоторый код со структурой, выровненной в одну сторону, а затем в другую, и вы обращаетесь к ней в цикле, скажем, миллион итераций, и вы определяете время. Довольно простая программа.   -  person Mitch Wheat    schedule 28.12.2013
comment
Это действительно зависит от архитектуры. Некоторые процессоры просто не могут обрабатывать невыровненные данные и, как следствие, требуют арифметических действий на программном уровне для разделения целого числа по границе выравнивания, что, очевидно, приводит к потере циклов.   -  person Mark H    schedule 28.12.2013
comment
Обратите внимание, что порядок не всегда сохраняется компилятором: -order-hey-were-decl" title="всегда ли элементы структуры класса создаются в памяти в том порядке, в котором они были decl"> stackoverflow.com/questions/281045/   -  person Alexei Averchenko    schedule 28.12.2013
comment
Если вы не можете измерить разницу в производительности, зачем вам это?   -  person Potatoswatter    schedule 28.12.2013
comment
Точка данных из реальной жизни: в Java JVM мы изменили способ распределения полей данных объекта Java, сортируя их по размеру (с некоторыми ограничениями). Результатом стало, IIRC, повышение производительности примерно на 10% по сравнению с серверным тестом только за счет экономии памяти. (В конечном счете, удалив лишнее пространство в строках и других стандартных объектах, мы добились общего повышения производительности на 30 %.)   -  person Hot Licks    schedule 02.01.2014


Ответы (2)


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

Рекомендуется сортировать элементы в порядке убывания размера для оптимальной упаковки/минимального расхода пространства на отступы, а не для выравнивания или скорости. Элементы будут правильно выровнены независимо от того, в каком порядке вы их перечисляете, если вы не запросите несовместимый макет, используя специализированные прагмы (например, непереносимые #pragma pack) или ключевые слова. Хотя общий размер структуры зависит от заполнения, а также влияет на скорость, часто существует другой оптимальный порядок.

Для достижения наилучшей производительности вы должны попытаться поместить элементы, которые используются вместе, в одну строку кэша, а элементы, к которым обращаются разные потоки, — в разные строки кэша. Иногда это означает много заполнения, чтобы получить одну общую переменную между потоками в своей собственной строке кэша. Но это лучше, чем снижение производительности из-за ложного обмена.

person Ben Voigt    schedule 28.12.2013
comment
Это первоклассный ответ - person Lightness Races in Orbit; 28.12.2013
comment
но также и для корректности. Невозможно упорядочить элементы данных в классе таким образом, чтобы элементы были выровнены неправильно; такие незаконно выровненные доступы являются отдельной проблемой, обычно возникающей из-за нарушений псевдонимов, а не из-за порядка элементов. - person bames53; 28.12.2013
comment
@bames53: я просто редактировал, чтобы уточнить это. Это возможно, но только с нестандартными прагмами упаковки, специфичными для компилятора. - person Ben Voigt; 28.12.2013
comment
Возможно, было бы полезно объяснить, почему сортировка помогает: компилятор будет соблюдать правила выравнивания, но также должен соблюдать порядок членов. Если вы сортируете по размеру, компилятору нужно вставить меньше отступов. Это связано с тем, что сортировка по размеру на практике равносильна сортировке по ограничениям выравнивания (именно это и следует делать — char[53] должен идти последним) - person MSalters; 28.12.2013
comment
@MSalters: Зависит от того, считаете ли вы char[53] одним объектом размером 53 или 53 объектами размером 1 каждый. Но да, заполнение действительно сводится к минимуму за счет группировки объектов с одинаковым выравниванием вместе. И это все еще не самое лучшее для производительности. - person Ben Voigt; 30.12.2013
comment
-1, A предполагает один шаблон использования, в некоторых случаях может быть быстрее иметь меньший размер (больше объектов помещается в кэш L *), а также ложное совместное использование может не быть проблемой, если вы используете данные определенным образом. Кроме того, даже для небольшого количества ложного обмена ложный обмен не является проблемой производительности... - person NoSenseEtAl; 31.12.2013
comment
@NoSenseEtAl: Конечно, доступ к некоторым структурам данных никогда не осуществляется из нескольких потоков. Я не понимаю, как это делает мой ответ неправильным. Я сказал, что члены, к которым обращаются из нескольких потоков, должны располагаться в отдельных строках кеша, если таких членов нет, это требование тривиально выполняется. Точно так же, если все элементы обычно используются вместе, то требование размещения элементов, используемых вместе, в как можно меньшем числе строк кэша тривиально выполняется за счет максимально плотной упаковки. Я не предполагаю конкретный шаблон использования, я считаю более простые случаи особыми случаями. - person Ben Voigt; 31.12.2013
comment
@NoSenseEtAl: Кроме того, я указал, что меньшая занимаемая площадь действительно влияет на производительность кэша, и что иногда небольшая занимаемая площадь оптимальна для производительности. Но часто это не так, и читателям важно понять, почему это не так, и какие другие соображения имеют значение. - person Ben Voigt; 31.12.2013
comment
Хотя общий размер структуры зависит от заполнения, а также влияет на скорость, часто существует другой оптимальный порядок. == я удаляю -1 :) - person NoSenseEtAl; 02.01.2014
comment
Я делаю это выравнивание только для того, чтобы украсить свой код, не зная, что это дает прирост производительности. - person mr5; 02.01.2014
comment
@ mr5 Что за выравнивание? Вы имеете в виду, что вы действительно думаете, что просмотр всех объявлений классов с сортировкой членов от наибольшего к наименьшему украшает код? Напротив, для нетривиальных классов объявления обычно выглядят ужасно и нелогично. Это означает, что вы должны постоянно проверять sizeof каждый тип члена, который вы используете, и обновлять его на основе этого. Что еще более важно, в некоторых случаях это просто невозможно, поскольку члены могут зависеть от того, что другие члены будут инициализированы первыми, независимо от того, какие у них sizeof. Ничто из этого не приводит к красивому коду или опыту написания кода. - person underscore_d; 13.08.2016
comment
Согласно прагме youtube.com/watch?v=BP6NxVxDQIs (начиная с 33-й минуты) pack больше не влияет на скорость на новых процессорах. - person user1911091; 29.07.2019
comment
@user1911091: user1911091: Я не смотрел, что это за претензия, но пакет прагмы по-прежнему влияет на правильность. Вы не можете использовать невыровненные переменные с большинством инструкций SSE. Вы не можете использовать невыровненные адреса с заблокированными инструкциями (префикс x86 LOCK). Соображения скорости вторичны по отношению к тому, чтобы программа работала. - person Ben Voigt; 29.07.2019

Просто чтобы добавить к отличному ответу Бена:

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

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

Предположим, что для архитектуры (я их не так хорошо знаю, думаю, что это было бы так для дефолтных настроек 32bit gcc, кто-то меня поправит в комментариях) такая структура:

struct MemoryUnused {
  uint8_t val0;
  uint16_t val1;
  uint8_t val2;
  uint16_t val3;
  uint8_t val4;
  uint32_t val5;
  uint8_t val6;
}

занимает 20 байт в памяти, а это:

struct MemoryNotLost {
  uint32_t val5;
  uint16_t val1;
  uint16_t val3;
  uint8_t val0;
  uint8_t val2;
  uint8_t val4;
  uint8_t val6;
}

Потребуется 12. Это 8 байтов, потерянных из-за заполнения, и это на 67% больше размера структуры меньшего размера. При большом массиве таких структур выигрыш будет значительным и, просто из-за объема используемой памяти, уменьшит количество промахов кеша.

person Dariusz    schedule 02.01.2014
comment
Хотя у вас есть теоретическая точка зрения, ваш аргумент кажется довольно немым во всех обстоятельствах, о которых я знаю: во-первых, только очень немногие struct всегда доступны в одном и том же порядке. Во-вторых, struct, который не помещается в кэш 1-го уровня, является настоящим монстром и никогда не должен производиться. В-третьих, у нас часто бывает много мелких объектов в больших массивах, и здесь для производительности имеет значение только одно: общий размер объектов. И в-четвертых, у нас в любом случае не должно быть несвязанных частей внутри одной структуры, что противоречит духу объектной ориентации. - person cmaster - reinstate monica; 02.01.2014
comment
@cmaster Во-первых, вопрос теоретический. Во-вторых, мы используем несколько char[] в структуре для хранения вводимых пользователем данных во внешнем интерфейсе — возможно, мы не гордимся этим, но это так. Указатели на несколько длинных строк были бы такими же плохими. В-третьих, в том-то и дело, что общий размер может быть уменьшен при заказе. В-четвертых, я не понимаю, к чему вы тут обращаетесь. Пост конечно не мой. - person Dariusz; 02.01.2014
comment
@cmaster: Наоборот ... всякий раз, когда вы составляете объекты для создания более крупного объекта, у вас есть операции (поведение подобъекта), которые обращаются только к подмножеству полного объекта. Это не противоречит духу ОО, это нормально. - person Ben Voigt; 02.01.2014
comment
И ваш вывод неверен. Если имеется большой массив таких структур и существует некоторый цикл, использующий элемент int32_t (чье имя странным образом изменилось с val5 на val0) и элемент int8_t val6, то последний вариант размещения памяти вызовет в два раза больше промахов кеша, чем первый (дополненный ) один. Вероятно, существует упакованный макет, который не страдает от этой проблемы, но простая сортировка по размеру не такова. - person Ben Voigt; 02.01.2014
comment
@BenVoigt Я изменил имена. Я не думал, что они имеют смысл, но теперь я понимаю, что они могут сбивать с толку. Спасибо за замечание. Ваша последняя заметка все еще актуальна? Если это так, пожалуйста, уточните, так как я не уверен, что понимаю это. - person Dariusz; 02.01.2014
comment
@cmaster - Термин спорный. - person Hot Licks; 02.01.2014
comment
@Dariusz: Сортируя по размеру, вы раздвинули val5 и val6 в памяти, поэтому загрузка одной строки кеша не будет содержать оба (ну, для достаточно большой строки кеша это может быть. Представьте, что структура больше, поэтому она не 'т). Если бы существовал цикл, который обращался только к этим двум элементам, то упакованная компоновка фактически работала бы медленнее, поскольку требовалось бы в два раза больше обращений к ОЗУ. - person Ben Voigt; 02.01.2014
comment
@BenVoigt Вы конечно правы - но это разные моменты. Вы можете подумать о том, чтобы оптимизировать первую для скорости, а другую — для минимизации использования памяти. - person Dariusz; 02.01.2014
comment
@BenVoigt - Но это предполагает, что они действительно будут доступны в таком порядке. Существует множество причин, по которым поля могут располагаться в определенном порядке, и вероятность того, что они перечислены в порядке ссылки, довольно мала. - person Hot Licks; 03.01.2014