Golang 内存对齐
-
对齐的目的: 方便
CPU进行快速的读取数据,通过空间换时间。 -
对齐系数:
32位系统对齐系数是4;64位系统对齐系数是8
unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数。和 Sizeof 类似, Alignof 也是返回一个常量表达式,对应一个常量。通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节),其它的类型对齐到机器字大小。
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Printf("int alignof is %d\n", unsafe.Alignof(int(0))) // 8
fmt.Printf("int32 alignof is %d\n", unsafe.Alignof(int32(0))) // 4
fmt.Printf("int64 alignof is %d\n", unsafe.Alignof(int64(0))) // 8
fmt.Printf("bool alignof is %d\n", unsafe.Alignof(bool(true))) // 1
}
- 如下是对应类型和所占用的字节
| 类型 | 大小 |
|---|---|
bool | 1个字节 |
intN, uintN, floatN, complexN | N/8个字节(例如float64是8个字节) |
int, uint, uintptr | 1个机器字 |
*T | 1个机器字 |
string | 2个机器字(data、len) |
[]T | 3个机器字(data、len、cap) |
map | 1个机器字 |
func | 1个机器字 |
chan | 1个机器字 |
interface | 2个机器字(type、value) |
对齐的原则
由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐。
- 字段对齐规则:字段的起始地址必须是其对齐单位的整数倍
- 每个字段只需要满足自己的对齐要求,不是为后续字段做对齐
- 结构体对齐规则:结构体的总大小必须是最大对齐单位的整数倍。
package main
import (
"fmt"
"runtime"
"unsafe"
)
type User struct {
IsMan bool // 1 byte
Age int16 // 2 byte
}
type User2 struct {
Height int8 // 1 byte
Money int64 // 8 byte
Age int32 // 4 byte
}
type User3 struct {
Height int8 // 1 byte
Age int32 // 4 byte
Money int64 // 8 byte
}
func main() {
fmt.Println(runtime.GOARCH) // amd64
u := User{}
fmt.Println(unsafe.Sizeof(u)) // 4 bytes
u2 := User2{}
fmt.Println(unsafe.Sizeof(u2)) // 24 bytes
u3 := User3{}
fmt.Println(unsafe.Sizeof(u3)) // 16 bytes
}
详解结构体中的内存布局情况
u1
u2
u3 内存对齐详细说明如下
Height: 占用1字节,从地址0开始;按照字段本身的对齐规则,Height占据内存地址[0:1];必须填充到4字节(因为下一个字段 Age 需要 4 字节对齐,其填充地址必须从 4 的整数倍开始)故需要填充3个字节,因此占用[0:4]Age: 占用4字节,从地址4开始,因此占用[4:8]Money: 占用8字节,从地址8开始(已经对齐到8字节)无需填充,因此占用[8:16]- 最大对齐单位是
8字节(由Money决定),总大小已经是16字节,无需额外填充