Когда создаешь структуру в Swift, интуитивно кажется, что ее размер должен равняться сумме размеров параметров. Int - 8 байт, String - 16, Bool - 1. Сложил и получил 25. Но на практике компилятор может добавить несколько скрытых байт, и тот же набор полей внезапно займет уже 32 байта. Все дело в выравнивании и пустых вставках между полями.
Как процессор читает память:
Процессор не работает с байтами по одному. Ему удобнее читать данные блоками, размер которых зависит от архитектуры. На 64-битных системах это обычно 8 байт. И важное условие: адрес, по которому лежит значение, должен быть кратен размеру этого значения. Int должен начинаться с адреса, кратного 8. String в Swift занимает 16 байт, но его внутренняя структура такова, что он также требует выравнивания на 8 байт. Bool может лежать где угодно.
Компилятор об этом знает и автоматически добавляет пустые байты (padding) между полями, чтобы соблюсти выравнивание. Из-за этого порядок объявления полей влияет на итоговый размер структуры.
Два примера - два размера:
Возьмем структуру с тремя полями разных типов:
struct First {
let flag: Bool // 1 байт
let name: String // 16 байт
let count: Int. // 8 байт
}
Поля идут так: Bool (1 байт), затем String (16 байт), но чтобы String начался с адреса, кратного 8, компилятор добавляет 7 пустых байт после Bool. Затем идет Int (8 байт), который уже сам по себе выровнен. Сумма: 1 + 7 + 16 + 8 = 32 байта. Конечный адрес структуры тоже должен быть выровнен по максимальному требованию (8 байт), у нас он уже кратен 8 и остается без изменений.
А теперь поменяем порядок:
struct Second {
let name: String // 16 байт
let count: Int. // 8 байт
let flag: Bool // 1 байт
}
String и Int идут первыми - оба с правильным выравниванием, никакого padding между ними не нужно. Bool в конце занимает 1 байт. Итог: 16 + 8 + 1 = 25 байт. Но теперь конечный адрес структуры не кратен 8, поэтому компилятор добавит ещё 7 байт в конец, чтобы выровнять структуру в памяти. Финальный размер - 32 байта.
Вроде бы оба варианта дали 32 байта? Да, но если добавить еще одно маленькое поле, разница станет заметной:
struct Third {
let flag: Bool // 1 байт
let name: String // 16 байт
let flag2: Bool // 1 байт
let count: Int. // 8 байт
}
Здесь после первого Bool добавляется 7 байт padding, затем String, потом flag2 (1 байт) и перед Int снова нужно добавить 7 байт padding. Итого: 1 + 7 + 16 + 1 + 7 + 8 = 40 байт.
А если сгруппировать правильно:
struct Fourth {
let name: String // 16 байт
let count: Int. // 8 байт
let flag: Bool // 1 байт
let flag2: Bool // 1 байт
}
Все крупные поля идут в начале, мелкие - в конце. Padding нужен только в конце для выравнивания всей структуры: 16 + 8 + 1 + 1 = 26 байт, плюс 6 байт в конце = 32 байта. Экономия 8 байт на каждой структуре.
Почему это важно:
В изолированной структуре разница в несколько байт не критична. Но когда таких структур тысячи в массиве или словаре, лишние байты превращаются в мегабайты лишней памяти. Особенно чувствительно это для кэшей, списков и любых данных, которые живут долго.
Кроме того, понимание выравнивания помогает при оптимизации: иногда достаточно просто переставить поля местами, чтобы структура похудела без потери функциональности. String, Int, Double требуют выравнивания по 8 байт, Bool и мелкие enum - по 1. Группируйте большие поля вместе, маленькие - в конец.
🔗 Ссылка на подробную статью
💡 Вывод:
Размер структуры в Swift зависит не только от типов полей, но и от порядка их объявления. Компилятор добавляет невидимые байты, чтобы данные лежали в памяти так, как удобно процессору. Знание этих правил позволяет писать более эффективный код, особенно когда речь идет о больших массивах структур. Иногда простая перестановка полей экономит мегабайты памяти без единой строчки дополнительной логики.
➡️ Подписаться на канал
Мобильный трудоголик