Оптимизация использования памяти в структурах Go
Go — язык системного уровня, и в нём память под структуры выделяется с учётом выравнивания (alignment). Понимание этого механизма позволяет сократить лишние затраты и сделать код эффективнее.
Лишние байты из-за выравнивания
Пример
1
2
3
4
5
6
7
8
type People struct {
Active bool
Height uint8
Weight uint8
Score int64
FirstName string
LastName string
}
На первый взгляд структура выглядит компактной: несколько чисел, строка и флаг. Но Go добавляет паддинги (заполнители пустых байтов), чтобы данные выравнивались по границам, удобным для процессора. Из-за этого итоговый размер структуры оказывается больше, чем можно подумать.
Проверяем размер структуры
Go позволяет узнать, сколько байт реально занимает структура
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import (
"fmt"
"unsafe"
)
type People struct {
Active bool
Height uint8
Weight uint8
Score int64
FirstName string
LastName string
}
func main() {
var p People
fmt.Println("Size of People:", unsafe.Sizeof(p))
}
👉 На 64-битной системе такой код выведет 64 байта.
Почему так много?
-
Поле bool занимает 1 байт, но дальше остаётся 7 байт паддинга, чтобы Score (int64) начинался с адреса, кратного 8.
-
Поля строк (string) — это указатели и длина, им также нужно выравнивание.
-
В результате структура сильно раздувается.
Оптимизация порядка полей
Если поменять порядок так, чтобы “крупные” типы шли первыми, а “мелкие” в конце, лишние паддинги исчезнут.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import (
"fmt"
"unsafe"
)
type People struct {
Score int64
FirstName string
LastName string
Active bool
Height uint8
Weight uint8
}
func main() {
var p People
fmt.Println("Size of People:", unsafe.Sizeof(p))
}
👉 Результат: 48 байт.
Мы сэкономили 25% памяти всего лишь перестановкой полей.
Общие правила оптимизации структур
-
Сначала большие поля (указатели, строки, слайсы, интерфейсы, int64, float64).
-
Мелкие числа (uint8, bool, int16 и т.д.) группируйте в конце.
-
Если есть массивы или вложенные структуры — учитывайте их выравнивание.
-
Объединяйте мелкие флаги в битовые маски.
❗️ Частая ошибка — хранить десятки bool внутри структуры. Каждый bool занимает 1 байт (и может тянуть за собой паддинг). Вместо этого лучше использовать uint8, uint32 или uint64 и хранить все флаги в битовом виде.
1
2
3
4
5
6
7
8
9
type Flags struct {
Bits uint64 // до 64 флагов
}
// установка бита
func (f *Flags) Set(pos uint) { f.Bits |= 1 << pos }
// проверка бита
func (f *Flags) Has(pos uint) bool { return f.Bits&(1<<pos) != 0 }
Такой подход позволяет сократить десятки байт на каждую запись в массиве структур.
- Выносите редко используемые поля в отдельную структуру (object splitting).
1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
// часто используемые поля
ID int64
Name string
// редко используемые
Extra *UserExtra
}
type UserExtra struct {
Bio string
Tags []string
}
Если часть полей используется редко, нет смысла хранить их прямо в основной структуре — это раздувает размер каждого объекта. Лучше вынести их в отдельный объект по указателю.
Таблица размеров и выравнивания типов в Go (64-битные системы)
Вывод
В Go расположение полей внутри структуры напрямую влияет на её размер в памяти. Грамотная перестановка и использование битовых масок вместо множества bool позволяет экономить десятки мегабайт при работе с большими коллекциями данных.
-
До: 64 байта
-
После: 48 байт
А если вместо 10 bool использовать один uint16, можно сэкономить ещё больше.

