结构是字段的类型化集合,可用于将数据分组到记录中。 这允许与一个实体相关的所有数据被巧妙地封装在一个轻量级类型定义中,然后可以通过在结构类型上定义函数来实现行为。
这篇文章我将尝试解释我们如何根据内存使用和 CPU 周期有效地编写结构。
让我们考虑下面的这个结构,为展示一些奇怪的用例,定义 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
}
让我们使用以下代码查看 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))
}
输出:
>> 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 类型的内存分配的样子
但是 88 个字节,16 +16 + 1 + 16 + 1+ 16 + 4 = 70 个字节怎么来的,这额外的 18 个字节是从哪里来的呢?
当涉及到结构的内存分配时,它们总是被分配连续的、字节对齐的内存块,并且按照定义的顺序分配和存储字段。 在这种情况下,字节对齐的概念意味着连续的内存块以等于平台字大小的偏移量对齐。
我们可以清楚地看到 TerraformResource.HaveDSL 、 TerraformResource.isVersionControlled 和 TerraformResource.ModuleVersionMajor 分别只占用 1 Byte、1Byte 和 4 Bytes。 其余空间被空填充字节填充。
现在重新计算一下占用字节
分配字节 = 16 字节 + 16 字节 + 16 字节 + 16 字节 +4 字节 + 1 字节 + 1 字节 = 70 字节
空填充字节 = 2 个字节
总字节 = 分配字节 + 空填充字节 = 70 字节 + 2 字节 = 72 字节
正确的数据结构对齐不仅可以帮助我们有效地使用内存,还可以帮助我们提高 CPU 读取周期......如何?
CPU 以字为单位读取内存,在 32 位系统上为 4 个字节,在 64 位系统上为 8 个字节。 现在我们第一次声明结构类型 TerraformResource 需要 11 个字让 CPU 读取所有内容
然而,优化后的结构将只占用 9 个单词,如下所示
通过正确定义数据结构对齐的结构,我们能够有效地使用内存分配,并使结构在 CPU 读取方面快速高效。
这只是一个小例子,想想一个有 20 或 30 个不同类型字段的大型结构。 数据结构的深思熟虑的对齐真的得到了回报......🤩
希望这个文章能够阐明结构内部结构、它们的内存分配和所需的 CPU 读取周期。 希望这可以帮助到你!!