Golang Написание эффективных с точки зрения памяти и оптимизированных с точки зрения процессора структур Go

Структура — это типизированная коллекция полей, полезная для группировки данных в записи. Это позволяет аккуратно заключить все данные, относящиеся к одному объекту, в одно легкое определение типа, а поведение можно реализовать, определив функции для типа 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 полями с различными типами. Продуманное выравнивание структуры данных действительно окупается… 🤩.

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

Счастливого кодинга!!!

Оцените статью
devanswers.ru
Добавить комментарий