• Уменьшение отступа

    Обратная связь

    (info@ru-sfera.pw)

Информация Зачем нужно выравнивание и заполнение структур ?


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 174
Всем привет!

Для новичков интересная тема, вот многие знают что компилятор выравнивает содержимое структур обычно кратно 4 в x32 и кратно 8 в x64.

Да есть разные атрибуты компилятора, которые запрещают это делать.

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

Итак, статья больше для новичков, но думаю будет интересно.)

Безымянный.png

Процессор и память​

Упрощенное представления взаимодействия процессора и памяти. Память имеет адресную байтовую последовательность и расположена последовательно. Чтение или запись данных в памяти выполняется посредством операций, которые воздействуют на одну ячейку за раз. Чтобы прочитать ячейку памяти или произвести запись в нее, мы должны передать ее числовой адрес. Память способна выполнять с адресом ячейки две операции: получить хранящееся в ней данные или записать новые. Память имеет специальный входной контакт для установки ее рабочего режима.

Группа проводов(линий), используемых для передачи одинаковых данных, называется шиной. Для передачи адреса используется адресная шина. А шина данных позволяет получать или записывать данные.

Безымянный2.png

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

У 32-разрядной шины каждый байт имеет 32-битный адрес, что позволит использовать 4Гб адресное пространство(2³²). С 64-битным адресом можно использовать до 2⁶⁴ байт.

При 32-разрядной шине за раз можно получить до 4 байта данных, а 64-разрядная шина позволяет прочитать сразу 8 байт. Такие куски информации принято называть машинным словом.

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

Адрес 2-байтового типа(int16) должен быть кратен 2, адрес 4-байтового значения(int32) должен быть кратен 4, а 8-байтового соответственно кратен 8.

Пример. У нас 64-разрядная архитектура, что позволяет за один раз прочитать до 8 байт. Сохраняем одно 1-байтовое значение и три 2-байтовых значения:

1685112111106.png


Один байт храниться по адресу 0, а 2-байтовые — 2, 4, 6 (кратные 2). Благодаря выравниванию не будет ситуации, когда придется делать два цикла чтения для получения одного значения:

1685112132110.png


Гарантии выравнивания типов (type alignment guarantees) также называют гарантиями выравнивания адресов(value address alignment guarantees). Если гарантией выравнивания типа T является N, то адрес каждого значения типа T должен быть кратным N во время выполнения. Можно также сказать, что адреса адресуемых значений типа T гарантированно выровнены по N-байтам.

К счастью, выравнивания и многое другое, гарантирует компилятор, и нам не нужно об этом беспокоиться. Но полезно помнить, что выравнивания делает обращение к памяти более эффективное, но вот использование памяти может стать менее эффективным.

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

Рассмотрим структуру:

Код:
type someData struct{
   a int8  // 1 byte
   b int64 // 8 byte
   c int8  // 1 byte
}

Размер значения этой структуры должен составлять суму ее полей. 1 + 8 + 1 =10 байт.

Но в реальности это не так.)

Результат совсем не тот, который ожидали. Структура занимает почти в 2.5 раза больше: 24 байт.)

Почему так ?

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

Неиспользованная память заполняется(padding) нулями.

Вот что получается в итоге:


1685112291423.png


Если элементам структуры задать a=0x1, b=0x2, c=0x3, то в памяти это будет выглядеть так:

Код:
Bytes are &[24]uint8{
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

Вот как-же можно оптимизировать использование памяти, не прибегая к различным флагам и расширения компилятора ?

Компилятор не меняет порядок полей в структуре и не может оптимизировать такие случае. А мы можем.​


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

Код:
type someDataV2 struct{
   a int8  // 1 byte
   c int8  // 1 byte
   b int64 // 8 byte
}

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

1685112566268.png


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

1685112599377.png


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

Также если вы работаете с железками, часто вам нужно запрещать выравнивать структуры, например это нужно при передаче данных в железку, для этого можно использовать attribute packed.

По материалам:
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 174
Предполагаю, что такие оптимизации где-то в малвари чаще всего
Нет.

Вот создал ты структуру, в которой, ну допустим 10 параметров....

При этом создаёшь кучу объектов этой структуры, вот на моей практике было что без оптимизации, в общей сложности создавалось около 200 мб. объектов, при этом с оптимизацией 40 мб.

Короче это нужно иметь в виду, особенно в системах, где ограничено потребление памяти, не везде программисты такие счастливые что могут для своей программы выделить гиг и больше и не заморачиваться оптимизацией.)
 
Автор темы Похожие темы Форум Ответы Дата
yum С++ 2
X-Shar С++ 3
Верх Низ