Что следует учитывать при выравнивании при проектировании пула памяти?

Я работаю над пулом памяти для небольшого игрового движка.

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

Проблема в том, что я начал немного нервничать, размышляя о выравнивании памяти. Я' m используется только для необработанного управления памятью на 8-битных процессорах, где все выровнено по байту.

Я разрешаю пользователю (мне) указать желаемый размер блоков, который в отдельном случае хранения будет размером объектов что я собираюсь сохранить в нем.

Текущий подход состоит в том, чтобы выделить кусок памяти блоков * (wanted_size + header_size) большой и поместить в него объекты с заголовком для каждого блока. ; Очевидно, что объекты будут располагаться непосредственно за этим заголовком.

Что мне нужно учитывать в отношении выравнивания памяти в моем сценарии?

Ответ, который я до сих пор придумал, заключается в том, что пока wanted_size представляет n -байтных данных; заголовок правильно выровнен и упакован компилятором, а также фактическими данными, все, что хранится в блоке, будет выровнено по n байт.

n - это любая граница, требуемая платформой. На данный момент я нацеливаюсь на x86, но я не люблю делать предположения о платформе в своем коде.

Некоторые из ресурсов, которые я использовал:

Редактировать

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

7
задан Community 23 May 2017 в 11:53
поделиться

5 ответов

Выделения с помощью malloc гарантированно будут выровнены для любого типа, предоставляемого компилятором, и, следовательно, для любого объекта [*].

Опасность заключается в том, что ваш заголовок требует меньшего выравнивания, чем максимальное требование выравнивания для вашей реализации. Тогда его размер может не быть кратным максимальному. выравнивание, и поэтому, когда вы пытаетесь преобразовать / использовать buf + header_size как указатель на то, что имеет макс. выравнивание, оно смещено. Что касается C, это неопределенное поведение. На Intel работает, но медленнее. На некоторых ARM это вызывает аппаратное исключение. На некоторых ARM молча дает неправильный ответ. Поэтому, если вы не хотите делать предположений о платформе в своем коде, вы должны с этим разобраться.

Есть три основных приема, о которых я знаю, чтобы гарантировать, что ваш заголовок не вызывает смещения:

  • Используйте прагму выравнивания, зависящую от реализации, чтобы вызвать проблему.
  • Используйте специфические для платформы знания о компоновке и выравнивании структуры, чтобы убедиться, что ее размер кратен максимальному требованию выравнивания в системе.Обычно это означает «вставьте дополнительный int в качестве заполнения, если необходимо, чтобы сделать его 8-кратным, а не просто 4-кратным».
  • Сделайте заголовок объединением каждого стандартного типа вместе со структурой, которую вы действительно хотите использовать. Хорошо работает на C, но у вас возникнут проблемы с C ++, если ваш заголовок недействителен для членства в союзах.

В качестве альтернативы, вы можете просто определить header_size не как sizeof (header) , а чтобы этот размер был округлен до кратного некоторой кратной степени 2, что "достаточно хорошо ". Если вы тратите немного памяти, пусть будет так, и вы всегда можете иметь «заголовок переносимости», который определяет такого рода вещи не только платформенно-независимым образом, но и упрощает адаптацию к новым платформам.

[*] с общим исключением: типы SIMD увеличенного размера. Поскольку они нестандартны, и было бы расточительно выравнивать по 16 каждое выделение только из-за них, они отмахиваются от них, и для них требуются специальные функции выделения.

7
ответ дан 6 December 2019 в 19:30
поделиться

Используйте http://msdn.microsoft.com/en-us/library/bb982233.aspx - std :: alignment_of. Это гарантирует, что для каждого T, который вы выделяете в свой пул, вы будете знать выравнивание и можете убедиться, что оно подходит. Я не собираюсь притворяться, что знаю / понимаю фактическую логику, которую вы бы использовали, чтобы превратить эту информацию в гарантии выравнивания, но она существует и доступна для использования.

1
ответ дан 6 December 2019 в 19:30
поделиться

Насколько я знаю, boost::pool основан на "аллокаторе малых объектов" Александреску, описанном в его книге "Современный дизайн c++". Я должен прочитать эту книгу (поскольку вы пишете этот материал для обучения)

.
1
ответ дан 6 December 2019 в 19:30
поделиться

Ваш компилятор уже выровняет элементы объектов и структур, которые будут храниться в пуле. Используется упаковка по умолчанию, соответствующая вашей архитектуре, обычно 8 для 32-битного ядра. Вам просто нужно убедиться, что адрес, который вы генерируете из своего пула, выровнен соответствующим образом. Что в 32-битной операционной системе должно быть кратно 8 байтам.

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

2
ответ дан 6 December 2019 в 19:30
поделиться

Я тоже возился с выравниванием памяти. Первое, что нужно понять, это то, что каждый объект имеет свои собственные требования к выравниванию памяти, которые (максимум) равны размеру самого объекта. К счастью, часто это меньше.

Существуют средства двух типов, помогающие написать правильные распределители памяти (в C++0x их можно найти в std::tr1 в большинстве STL):

  • std::alignment_of дает выравнивание как значение времени компиляции
  • std::aligned_storage дает выровненное хранилище в соответствии с его параметрами

Работая с ними двумя, вы получите то, что вам нужно.

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

template <class T, size_t N>
struct Block
{
  // Header bits

  typedef std::aligned_storage<
      N*sizeof(T),
      std::alignment_of<T>::value
  >::type buffer_type;
  buffer_type mBuffer;
};

Два замечания:

  • Сам блок может иметь другое выравнивание, но это не имеет значения
  • Вы можете использовать sizeof для выравнивания, это гарантированно работает, даже если немного расточительно

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

2
ответ дан 6 December 2019 в 19:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: