[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Структуры — это коллекции разнородных данных, определенные программистами для организации информации. Структуры позволяют аккуратно инкапсулировать все данные, относящиеся к одному объекту, в одно определение типа, поведение которого затем может быть реализовано путём определения функций для типа struct.

В этой статье я попытаюсь объяснить, как мы можем эффективно написать структуру с точки зрения использования памяти и циклов процессора.

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Давайте рассмотрим структуру TerraformResource, которая предоставлена ниже:

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
}

Давайте посмотрим, сколько памяти требуется для данной структуры, используя приведённый ниже код:

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))  
}

Результат

>> go run golang-struct-memory-allocation.go
==============================================================
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:

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Но как получилось 88 байт (16 +16 + 1 + 16 + 1+ 16 + 4 = 70 байт)? Откуда берутся эти дополнительные 18 байт?

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

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Мы можем ясно видеть, что TerraformResource.HaveDSL , TerraformResource.isVersionControlled и TerraformResource.ModuleVersionMajor занимают всего 1 байт, 1 байт и 4 байта соответственно. Остальная часть пространства заполняется пустыми байтами заполнения.

Итак, возвращаясь к математике:

Байты выделения =16 байт +16 байт + 1 байт+ 16 байт+ 1 байт + 16 байт + 4 байта = 70 байт

Пустые байты заполнения = 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", dModuleVersionMajor, 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 байта. Давайте посмотрим, как выглядит выравнивание памяти:

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Просто выполнив правильное выравнивание структуры данных для элементов struct, мы смогли уменьшить объем памяти с 88 байт до 72 байт…Великолепно!

Снова обращаемся к математике:

Байты выделения = 16 байт + 16 байт + 16 байт + 16 байт +4 байта + 1 байт + 1 байт = 70 байт

Пустые байты заполнения = 2 байта

Общее количество байтов = байты выделения + пустые байты заполнения = 70 байт + 2 байта = 72 байта

Правильное выравнивание структуры данных помогает нам не только эффективно использовать память, но и сокращать циклы чтения ЦП….Как?

Процессор считывает память словами, которые составляют 4 байта в 32-разрядных системах, 8 байт в 64-разрядных системах. Теперь нашему первому объявлению типа структуры TerraformResource потребуется слов, чтобы процессор считал всё:

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

Однако оптимизированная структура займёт всего слов, как показано ниже:

[Golang] Написание эффективных для памяти и процессора Go-структур, оптимизированных для работы с ЦП 2023

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

Это всего лишь небольшой пример; подумайте о большой структуре с 20 или 30 полями разных типов. Продуманное выравнивание структуры данных действительно является эффективным лайфхаком.

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

+1
1
+1
10
+1
1
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *