Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 26, 2022 03:26 am GMT

Golang Writing memory efficient and CPU optimized Go Structs

A struct is a typed collection of fields, useful for grouping data into records. This allows all the data relating to one entity to be neatly encapsulated in one lightweight type definition, behavior can then be implemented by defining functions on the struct type.

This blog I will try to explain how we can efficiently write struct in terms of Memory Usages and CPU Cycles.

Lets consider this struct below, definition of terraform resource type for some weird use-case I have:

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}

Let see how much memory allocation is required for the TerraformResource struct using code below:

package mainimport "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]
", d, unsafe.Sizeof(d)) fmt.Println("==============================================================") fmt.Printf("Cloud Field StructType:d.Cloud %T => [%d]
", d.Cloud, unsafe.Sizeof(d.Cloud)) fmt.Printf("Name Field StructType:d.Name %T => [%d]
", d.Name, unsafe.Sizeof(d.Name)) fmt.Printf("HaveDSL Field StructType:d.HaveDSL %T => [%d]
", d.HaveDSL, unsafe.Sizeof(d.HaveDSL)) fmt.Printf("PluginVersion Field StructType:d.PluginVersion %T => [%d]
", d.PluginVersion, unsafe.Sizeof(d.PluginVersion)) fmt.Printf("ModuleVersionMajor Field StructType:d.IsVersionControlled %T => [%d]
", d.IsVersionControlled, unsafe.Sizeof(d.IsVersionControlled)) fmt.Printf("TerraformVersion Field StructType:d.TerraformVersion %T => [%d]
", d.TerraformVersion, unsafe.Sizeof(d.TerraformVersion)) fmt.Printf("ModuleVersionMajor Field StructType:d.ModuleVersionMajor %T => [%d]
", d.ModuleVersionMajor, unsafe.Sizeof(d.ModuleVersionMajor)) }

Output

==============================================================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]

So total memory allocation required for the TerraformResource struct is 88 bytes. This is how the memory allocation will look like for TerraformResource type

But how come 88 bytes, 16 +16 + 1 + 16 + 1+ 16 + 4 = 70 bytes, where is this additional 18 bytes coming from ?

When it comes to memory allocation for structs, they are always allocated contiguous, byte-aligned blocks of memory, and fields are allocated and stored in the order that they are defined. The concept of byte-alignment in this context means that the contiguous blocks of memory are aligned at offsets equal to the platforms word size.

We can clearly see that TerraformResource.HaveDSL , TerraformResource.isVersionControlled and TerraformResource.ModuleVersionMajor are only occupying 1 Byte, 1 Byte and 4 Bytes respectively. Rest of the space is fill with empty pad bytes.

So going back to same math

Allocation bytes = 16 bytes + 16 bytes + 1 byte + 16 bytes + 1 byte + 16 byte + 4 bytes

Empty Pad bytes = 7 bytes + 7 bytes + 4 bytes = 18 bytes

Total bytes = Allocation bytes + Empty Pad bytes = 70 bytes + 18 bytes = 88 bytes

So, How do we fix this ? With proper data structure alignment what if we redefine our struct like this

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}

Run the same Code with optimized struct

package mainimport "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]
", d, unsafe.Sizeof(d)) fmt.Println("==============================================================") fmt.Printf("Cloud Field StructType:d.Cloud %T => [%d]
", d.Cloud, unsafe.Sizeof(d.Cloud)) fmt.Printf("Name Field StructType:d.Name %T => [%d]
", d.Name, unsafe.Sizeof(d.Name)) fmt.Printf("HaveDSL Field StructType:d.HaveDSL %T => [%d]
", d.HaveDSL, unsafe.Sizeof(d.HaveDSL)) fmt.Printf("PluginVersion Field StructType:d.PluginVersion %T => [%d]
", d.PluginVersion, unsafe.Sizeof(d.PluginVersion)) fmt.Printf("ModuleVersionMajor Field StructType:d.IsVersionControlled %T => [%d]
", d.IsVersionControlled, unsafe.Sizeof(d.IsVersionControlled)) fmt.Printf("TerraformVersion Field StructType:d.TerraformVersion %T => [%d]
", d.TerraformVersion, unsafe.Sizeof(d.TerraformVersion)) fmt.Printf("ModuleVersionMajor Field StructType:d.ModuleVersionMajor %T => [%d]
", d.ModuleVersionMajor, unsafe.Sizeof(d.ModuleVersionMajor))}

Output

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]

Now total memory allocation for the TerraformResource type is 72 bytes. Lets see how the memory alignments looks likes

Just by doing proper data structure alignment for the struct elements we were able to reduce the memory footprint from 88 bytes to 72 bytes....Sweet!!

Lets check the math

Allocation bytes = 16 bytes + 16 bytes + 16 bytes + 16 bytes + 4 bytes + 1 byte + 1 bytes = 70 bytes

Empty Pad bytes = 2 bytes

Total bytes = Allocation bytes + Empty Pad bytes = 70 bytes + 2 bytes = 72 bytes

Proper data structure alignment not only helps us use memory efficiently but also with CPU Read Cycles.How ?

CPU Reads memory in words which is 4 bytes on a 32-bit, 8 bytes on a 64-bit systems. Now our first declaration of struct type TerraformResource will take 11 Words for CPU to read everything

However the optimized struct will only take 9 Words as shown below

By defining out struct properly data structured aligned we were able to use memory allocation efficiently and made the struct fast and efficient in terms of CPU Reads as well.

This is just a small example, think about a large struct with 20 or 30 fields with different types. Thoughtful alignment of data structure really pays off

Hope this blog was able to shed some light on struct internals, their memory allocations and required CPU reads cycles. Hope this helps!!

Happy Coding!!


Original Link: https://dev.to/deadlock/golang-writing-memory-efficient-and-cpu-optimized-go-structs-2ick

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To