Структура — это типизированная коллекция полей, полезная для группировки данных в записи. Это позволяет аккуратно заключить все данные, относящиеся к одному объекту, в одно легкое определение типа, а поведение можно реализовать, определив функции для типа struct.
В этом блоге я попытаюсь объяснить, как мы можем эффективно писать struct с точки зрения использования памяти и циклов процессора.
Давайте рассмотрим эту структуру ниже, определение типа ресурса terraform для некоторого странного случая использования, который у меня есть:
type TerraformResource struct {
Cloud string // 16 bytes
Name string // 16 bytes
HaveDSL bool // 1 byte
PluginVersion string // 16 bytes
IsVersionControlled bool // 1 byte
TerraformVersion string // 16 bytes
ModuleVersionMajor int32 // 4 bytes
}
Давайте посмотрим, сколько памяти необходимо выделить для структуры TerraformResource
, используя приведенный ниже код:
package main
import "fmt"
import "unsafe"
type TerraformResource struct {
Cloud string // 16 bytes
Name string // 16 bytes
HaveDSL bool // 1 byte
PluginVersion string // 16 bytes
IsVersionControlled bool // 1 byte
TerraformVersion string // 16 bytes
ModuleVersionMajor int32 // 4 bytes
}
func main() {
var d TerraformResource
d.Cloud = "aws"
d.Name = "ec2"
d.HaveDSL = true
d.PluginVersion = "3.64"
d.TerraformVersion = "1.1"
d.ModuleVersionMajor = 1
d.IsVersionControlled = true
fmt.Println("==============================================================")
fmt.Printf("Total Memory Usage StructType:d %T => [%d]n", d, unsafe.Sizeof(d))
fmt.Println("==============================================================")
fmt.Printf("Cloud Field StructType:d.Cloud %T => [%d]n", d.Cloud, unsafe.Sizeof(d.Cloud))
fmt.Printf("Name Field StructType:d.Name %T => [%d]n", d.Name, unsafe.Sizeof(d.Name))
fmt.Printf("HaveDSL Field StructType:d.HaveDSL %T => [%d]n", d.HaveDSL, unsafe.Sizeof(d.HaveDSL))
fmt.Printf("PluginVersion Field StructType:d.PluginVersion %T => [%d]n", d.PluginVersion, unsafe.Sizeof(d.PluginVersion))
fmt.Printf("ModuleVersionMajor Field StructType:d.IsVersionControlled %T => [%d]n", d.IsVersionControlled, unsafe.Sizeof(d.IsVersionControlled))
fmt.Printf("TerraformVersion Field StructType:d.TerraformVersion %T => [%d]n", d.TerraformVersion, unsafe.Sizeof(d.TerraformVersion))
fmt.Printf("ModuleVersionMajor Field StructType:d.ModuleVersionMajor %T => [%d]n", d.ModuleVersionMajor, unsafe.Sizeof(d.ModuleVersionMajor))
}
Выход
==============================================================
Total Memory Usage StructType:d main.TerraformResource => [88]
==============================================================
Cloud Field StructType:d.Cloud string => [16]
Name Field StructType:d.Name string => [16]
HaveDSL Field StructType:d.HaveDSL bool => [1]
PluginVersion Field StructType:d.PluginVersion string => [16]
ModuleVersionMajor Field StructType:d.IsVersionControlled bool => [1]
TerraformVersion Field StructType:d.TerraformVersion string => [16]
ModuleVersionMajor Field StructType:d.ModuleVersionMajor int32 => [4]
Таким образом, общее выделение памяти, необходимое для структуры TerraformResource, составляет 88 байт. Вот как будет выглядеть распределение памяти для типа TerraformResource
Но почему 88 байт, 16 +16 + 1 + 16 + 1 + 16 + 16 + 4
= 70 байт
, откуда возьмутся эти дополнительные 18 байт
?
Когда речь идет о распределении памяти для структур, им всегда выделяются смежные блоки памяти с выравниванием по байтам, а поля выделяются и хранятся в том порядке, в котором они определены. Понятие выравнивания по байтам в данном контексте означает, что смежные блоки памяти выравниваются по смещениям, равным размеру слова платформы.
Мы видим, что TerraformResource.HaveDSL
, TerraformResource.isVersionControlled
и TerraformResource.ModuleVersionMajor
занимают только 1 байт
, 1 байт
и 4 байта
соответственно. Остальное пространство заполнено пустыми байтами.
Таким образом, возвращаясь к той же математике
Allocation bytes =
16 bytes
+16 bytes
+1 byte
+16 bytes
+1 byte
+16 byte
+4 bytes
.Пустой блокнот =
7 байт
+7 байт
+4 байт
=18 байт
Всего байт = байт распределения + байт пустого блока =
70 байт
+18 байт
=88 байт
Итак, как мы можем это исправить? При правильном выравнивании структуры данных, что если мы переопределим нашу структуру следующим образом
type TerraformResource struct {
Cloud string // 16 bytes
Name string // 16 bytes
PluginVersion string // 16 bytes
TerraformVersion string // 16 bytes
ModuleVersionMajor int32 // 4 bytes
HaveDSL bool // 1 byte
IsVersionControlled bool // 1 byte
}
Запустить тот же код с оптимизированной структурой
package main
import "fmt"
import "unsafe"
type TerraformResource struct {
Cloud string // 16 bytes
Name string // 16 bytes
PluginVersion string // 16 bytes
TerraformVersion string // 16 bytes
ModuleVersionMajor int32 // 4 bytes
HaveDSL bool // 1 byte
IsVersionControlled bool // 1 byte
}
func main() {
var d TerraformResource
d.Cloud = "aws"
d.Name = "ec2"
d.HaveDSL = true
d.PluginVersion = "3.64"
d.TerraformVersion = "1.1"
d.ModuleVersionMajor = 1
d.IsVersionControlled = true
fmt.Println("==============================================================")
fmt.Printf("Total Memory Usage StructType:d %T => [%d]n", d, unsafe.Sizeof(d))
fmt.Println("==============================================================")
fmt.Printf("Cloud Field StructType:d.Cloud %T => [%d]n", d.Cloud, unsafe.Sizeof(d.Cloud))
fmt.Printf("Name Field StructType:d.Name %T => [%d]n", d.Name, unsafe.Sizeof(d.Name))
fmt.Printf("HaveDSL Field StructType:d.HaveDSL %T => [%d]n", d.HaveDSL, unsafe.Sizeof(d.HaveDSL))
fmt.Printf("PluginVersion Field StructType:d.PluginVersion %T => [%d]n", d.PluginVersion, unsafe.Sizeof(d.PluginVersion))
fmt.Printf("ModuleVersionMajor Field StructType:d.IsVersionControlled %T => [%d]n", d.IsVersionControlled, unsafe.Sizeof(d.IsVersionControlled))
fmt.Printf("TerraformVersion Field StructType:d.TerraformVersion %T => [%d]n", d.TerraformVersion, unsafe.Sizeof(d.TerraformVersion))
fmt.Printf("ModuleVersionMajor Field StructType:d.ModuleVersionMajor %T => [%d]n", d.ModuleVersionMajor, unsafe.Sizeof(d.ModuleVersionMajor))
}
Вывести
go run golang-struct-memory-allocation-optimized.go
==============================================================
Total Memory Usage StructType:d main.TerraformResource => [72]
==============================================================
Cloud Field StructType:d.Cloud string => [16]
Name Field StructType:d.Name string => [16]
HaveDSL Field StructType:d.HaveDSL bool => [1]
PluginVersion Field StructType:d.PluginVersion string => [16]
ModuleVersionMajor Field StructType:d.IsVersionControlled bool => [1]
TerraformVersion Field StructType:d.TerraformVersion string => [16]
ModuleVersionMajor Field StructType:d.ModuleVersionMajor int32 => [4]
Теперь общее распределение памяти для типа TerraformResource
составляет 72 байта
. Давайте посмотрим, как выглядит выравнивание памяти.
Просто выполнив правильное выравнивание структуры данных для элементов struct, мы смогли уменьшить объем памяти с 88 байт
до 72 байт
….Сладко!!!
Давайте проверим математику
Байт распределения =
16 байт
+16 байт
+16 байт
+16 байт
+16 байт
+4 байт
+1 байт
+1 байт
=70 байт
.Байты пустой площадки =
2 байта
Всего байт = байт распределения + байт пустой площадки =
70 байт
+2 байта
=72 байта
Правильное выравнивание структуры данных не только помогает нам эффективно использовать память, но и сокращает количество циклов чтения процессора.
Процессор читает память словами, что составляет 4 байта
на 32-битных
, 8 байт
на 64-битных
системах. Теперь наше первое объявление типа struct TerraformResource займет 11 слов
для CPU, чтобы прочитать все.
Однако оптимизированная структура займет только 9 слов
, как показано ниже.
Определив struct должным образом, мы смогли эффективно использовать распределение памяти и сделали struct быстрым и эффективным с точки зрения CPU Reads.
Это лишь небольшой пример, подумайте о большой структуре с 20 или 30 полями с различными типами. Продуманное выравнивание структуры данных действительно окупается… 🤩.
Надеюсь, этот блог смог пролить свет на внутреннее устройство структур, их распределение памяти и требуемые циклы чтения процессора. Надеюсь, это поможет!!!
Счастливого кодинга!!!