更新时间:

#在 Go 中恰到好处的内存对齐 fieldalignment

重排序、指令对齐

type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}
占用内存:
func main() {
    fmt.Printf("bool size: %d\n", unsafe.Sizeof(bool(true)))
    fmt.Printf("int32 size: %d\n", unsafe.Sizeof(int32(0)))
    fmt.Printf("int8 size: %d\n", unsafe.Sizeof(int8(0)))
    fmt.Printf("int64 size: %d\n", unsafe.Sizeof(int64(0)))
    fmt.Printf("byte size: %d\n", unsafe.Sizeof(byte(0)))
    fmt.Printf("string size: %d\n", unsafe.Sizeof("EDDYCJY"))
}

bool size: 1 int32 size: 4 int8 size: 1 int64 size: 8 byte size: 1 string size: 16 这么一算,Part1 这一个结构体的占用内存大小为 1+4+1+8+1 = 15 个字节。相信有的小伙伴是这么算的,看上去也没什么毛病

func main() {
    part1 := Part1{}

    fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
}
实际结果  part1 size: 32, align: 8

#内存对齐。 cpu 每次是8字节(64位)读取到寄存器

#对齐规则

  • 结构体的成员变量,第一个成员变量的偏移量为 0。往后的每个成员变量的对齐值必须为编译器默认对齐长度(#pragma pack(n))或当前成员变量类型的长度(unsafe.Sizeof),取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍
  • 结构体本身,对齐值必须为编译器默认对齐长度(#pragma pack(n))或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值
  • 结合以上两点,可得知若编译器默认对齐长度(#pragma pack(n))超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

 最终结果 Part1 内存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx

改造后 
type Part2 struct {
    e byte
    c int8
    a bool
    b int32
    d int64
}
Part2 内存布局:ecax|bbbb|dddd|dddd

#总结

通过对比 Part1 和 Part2 的内存布局,你会发现两者有很大的不同。如下: Part1:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx Part2:ecax|bbbb|dddd|dddd


go编译器 没自动调整?

#提供了fieldalignment 工具

go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest

  • fieldalignment ./xxx/xxx
  • fieldalignment -fix ./xxx/xxx

参考