更新时间:
#在 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