Go内存对齐
如何得到一个对象所占内存大小?
fmt.Println(unsafe.Sizeof(int64(0))) // "8" type SizeOfA struct { A int } unsafe.Sizeof(SizeOfA{0}) // 8 type SizeOfC struct { A byte // 1字节 C int32 // 4字节 } unsafe.Sizeof(SizeOfC{0, 0}) // 8 unsafe.Alignof(SizeOfC{0, 0}) // 4 结构体中A byte占1字节,C int32占4字节. SizeOfC占8字节
内存对齐:
为何会有内存对齐?
1.并不是所有硬件平台都能访问任意地址上的任意数据。
2.性能原因 访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存只需访问一次。
上面代码SizeOfC中元素一共5个字节,而实际结构体占8字节
是因为这个结构体的对齐倍数Alignof(SizeOfC) = 4.也就是说,结构体占的实际大小必须是4的倍数,也就是8字节。
type SizeOfD struct { A byte B [3]int32 } unsafe.Sizeof(SizeOfD{}) // 16 unsafe.Alignof(SizeOfD{}) // 4
Alignof返回的对齐数是结构体中单位基本类型所占的内存数,不超过8,如果元素是数组那么取数组元素类型所占的内存值而不是整个数组的值。
如图为SizeofD结构体变量的内存布局:
type SizeOfE struct { A byte // 1 B int64 // 8 C byte // 1 } unsafe.Sizeof(SizeOfE{}) // 24 unsafe.Alignof(SizeOfE{}) // 8
SizeOfE中,元素的大小分别为1,8,1,但是实际结构体占24字节,远超元素实际大小,因为内存对齐原因,最开始分配的8字节中包含了1字节的A,剩余的7字节不足以放下B,又为B分配了8字节,剩余的C独占再分配的8字节。
如图为SizeofE结构体变量的内存布局:
type SizeOfF struct { A byte // 1 C byte // 1 B int64 // 8 } unsafe.Sizeof(SizeOfF{}) // 16 unsafe.Alignof(SizeOfF{}) // 8
换一种写法,把A,C放到上面,B放到下面。这时SizeOfE占用的内存变为了16字节。因为首先分配的8字节足以放下A和C,省去了8字节的空间。
上面一个结构体中元素的不同顺序足以导致内存分配的巨大差异。前一种写法产生了很多的内存空洞,导致结构体不够紧凑,造成内存浪费。如图为SizeofF结构体变量的内存布局:
下面我们来看一下结构体中元素的内存布局:
unsafe.Offsetof:返回结构体中元素所在内存的偏移量
type SizeOfH struct { A byte C int16 B int64 D int32 } unsafe.Offsetof(SizeOfH{}.A) // 0 unsafe.Offsetof(SizeOfH{}.C) // 2 unsafe.Offsetof(SizeOfH{}.B) // 8 unsafe.Offsetof(SizeOfH{}.D) // 16
下图为SizeOfH 内存布局图:
蓝色区域是元素实际所占内存,灰色为内存空洞。
下面总结一下go语言中各种类型所占内存大小(x64环境下):
X64下1机器字节=8字节
总结一下:
从例子中可以看出,结构体中元素不同顺序的排列会导致内存分配的极大差异,不好的顺序会产生许多的内存空洞,造成大量内存浪费。
虽然这几个函数都在unsafe包中,但是他们并不是不安全的。在需要优化内存空间时这几个函数非常有用。