3.6 内存对齐、内存布局是怎么回事?¶
字长(word size)¶
字长(word size),指的是 CPU 一次可以访问数据的最大长度:
对于 32 位的 cpu 来说:word size 为
2^32
,即 4 byte对于 64 位的 cpu 来说:word size 为
2^64
,即 8 byte
两种内存布局¶
下边是一个结构体的两种内存布局方式
第一种:按顺序¶
代码
type Foo struct {
A int8 // 1
B int8 // 1
C int8 // 1
}
type Bar struct {
x int32 // 4
y *Foo // 8
z bool // 1
}
你觉得 Bar 对象会占用多少的内存? 可能很多人会下意识地回答 13
会回答 13 是因为你觉得该结构体的内存分配是下面这样按顺利分配的。
按照前面所介绍的 word size 为 8 来计算,使用这种分配方式,当你访问 bar.y 的时候,CPU 需要访问内存两次。
第二种:按字长¶
而如果使用下面这种方式,当你再次访问 bar.y 的时候,CPU 需要访问内存一次。
因此真正的答案是 24,这是一种典型的用空间换时间的方法 – 内存对齐
func main() {
var bar Bar
fmt.Println(unsafe.Sizeof(bar)) // 24
}
合理定义结构体¶
从以上可以发现,我们定义的结构体虽然不大,只占用 24个byte,但实际有用的只有 13 的byte,内存使用率只有 50% 左右,很有优化的必要性。
如果将第三个属性挪 y
的前面
那就可以省下来 1 个 byte 了
func main() {
var bar Bar
fmt.Println(unsafe.Sizeof(bar)) // 16
}
y 为什么占用 8 字节?¶
看完了上面的介绍,想必你一定有一个疑问: Foo 结构体实际占用 3个byte,为什么 Bar.y 却要占用 8个 byte 呢?
type Foo struct {
A int8 // 1
B int8 // 1
C int8 // 1
}
type Bar struct {
x int32 // 4
y *Foo // 8
z bool // 1
}
因为 Bar.y
表示的是一个指针,而指针的对齐系数是 8
func main() {
var bar Bar
fmt.Println(unsafe.Alignof(bar.y)) // 8
}
你大可将 y 改成普通对象
type Foo struct {
A int8 // 1
B int8 // 1
C int8 // 1
}
type Bar struct {
x int32 // 4
y Foo // 3
z bool // 1
}
这样一来,bar 对象就只占用一个字长
func main() {
var bar Bar
fmt.Println(unsafe.Sizeof(bar)) // 8
}