В структуре переставили поля, не изменив их типы. При каких условиях это уменьшит расход памяти программы?
Перестановка полей уменьшает размер структуры, если она сокращает выравнивающие промежутки — padding, которые компилятор добавляет между полями и в конце структуры. Это особенно заметно при смешивании полей с разными требованиями к выравниванию и при создании больших массивов или срезов таких структур.
Выравнивание нужно процессору для эффективного и иногда обязательного доступа к многобайтным значениям. Поэтому Go размещает поля с учётом требований конкретной архитектуры, а не просто последовательно складывает их размеры.
Такой layout позволяет выполнять доступ к полям предсказуемо и эффективно, но может добавлять неиспользуемые байты. Явное управление размещением полей появилось как практическая необходимость для системного языка, работающего с большими объёмами структурированных данных.
Если структура занимает больше памяти, чем необходимо по сумме размеров её полей, это увеличивает стоимость массивов, срезов, копирования и чтения данных из памяти. Например, небольшой избыток в одной структуре становится существенным при миллионах элементов.
Неверно считать, что перестановка полей всегда помогает. Она не меняет размер типов, для которых padding уже отсутствует, а итоговый layout зависит от архитектуры и конкретных требований выравнивания. Кроме того, уменьшение размера структуры не обязательно заметно ускорит программу, если узким местом является не память, а вычисления или случайный доступ.
Компилятор размещает каждое поле на смещении, подходящем для его выравнивания. Если текущее смещение не подходит, между предыдущим и следующим полем добавляются padding-байты. В конце структуры размер также округляется до выравнивания структуры, чтобы корректно размещать элементы массива.
Например, на типичной 64-битной платформе структура с полями bool, int64, bool обычно занимает 24 байта: перед int64 и в конце появляются промежутки. При размещении int64, bool, bool она обычно занимает 16 байт.
unsafe.Sizeof показывает размер значения, включая padding, но результат следует проверять на целевой архитектуре. Если такие структуры хранятся в срезе, уменьшение размера снижает объём выделенной памяти и улучшает плотность данных в кэше CPU.
Для сборщика мусора важен ещё один аспект: плотность указателей и наличие указательных полей. Перестановка полей сама по себе обычно не превращает указатель в неуказатель и не устраняет необходимость сканировать его, но уменьшение размера элементов снижает общий объём памяти и может уменьшить количество загружаемых из памяти данных. Если структура содержит много указателей, основная стоимость GC определяется прежде всего их количеством и достижимостью, а не только padding.
Практическое правило — группировать поля с похожими требованиями к выравниванию, обычно размещая крупные поля раньше мелких. Однако менять порядок нужно после проверки размера и профилирования: ухудшение читаемости или совместимости с внешним бинарным layout может оказаться дороже экономии памяти.
В сервисе хранился срез из нескольких миллионов записей. В каждой записи были флаг состояния, 64-битный идентификатор и ещё один флаг. Анализ показал, что из-за порядка полей элемент занимал 24 байта, хотя альтернативный порядок позволял разместить его в 16 байтах на целевой платформе.
Рассматривались три варианта:
Выбрали перестановку полей и отдельно проверили бинарную сериализацию, потому что структура не являлась публичным wire-форматом. В результате уменьшились объём среза и давление на кэш; измеримый эффект проявился именно в операциях, обрабатывавших большие пачки записей. Для одиночных запросов заметного ускорения не было.
Вопрос: Почему нельзя надёжно вычислять размер структуры простой суммой размеров её полей?
Ответ: Между полями и в конце структуры может находиться padding, необходимый для выравнивания. Кроме того, размер и выравнивание некоторых типов зависят от архитектуры, поэтому для проверки конкретного layout используют unsafe.Sizeof и инструменты анализа, а не арифметику по документации типов.
Вопрос: Уменьшит ли перестановка полей число объектов, которые сканирует GC?
Ответ: Нет, сама перестановка не меняет количество экземпляров и указательных полей. Она может уменьшить размер каждого объекта, объём памяти и косвенно стоимость работы с кучей, но стоимость маркировки в первую очередь связана с достижимыми объектами и указателями, которые нужно обработать.
Вопрос: Почему изменение порядка полей опасно для структуры, используемой во внешнем бинарном формате?
Ответ: Порядок полей и padding могут быть частью формата, даже если они не описаны явно. После перестановки памяти структура может иметь другой layout, поэтому данные, записанные напрямую через небезопасное преобразование или зависящие от фиксированных смещений, станут несовместимыми. Для внешних форматов следует явно сериализовать поля, а не полагаться на внутреннее размещение Go.