什么是内存对齐
内存对齐对程序员来说,一般是“透明的”,它属于编译器行为,编译器会将程序中的每个“数据单元”安排在适当的位置上。
为什么要内存对齐
CPU
读取数据的时候,不是一个字节一个字节读取的,而是一块一块读取的,具体的块的大小是根据 CPU
的位数来区分的。为了读取更快,减少 CPU
访问内存的次数(典型的空间换时间的做法),所以需要内存对齐。
如何查看数据占用内存大小?
func main() {
// 64位
// bool 1
// int8 1
// int16 2
// int 8
// int32 4
// string 16
// []int 24
fmt.Println("bool: ", unsafe.Sizeof(true))
fmt.Println("int8: ", unsafe.Sizeof(int8(0)))
fmt.Println("int16: ", unsafe.Sizeof(int16(10)))
fmt.Println("int32: ", unsafe.Sizeof(int32(190)))
fmt.Println("int: ", unsafe.Sizeof(int(10))) // int 是可变的,在32位系统上是4,在64位系统上是8
fmt.Println("string: ", unsafe.Sizeof("asong"))
fmt.Println("[]int: ", unsafe.Sizeof([]int{1, 3}))
fmt.Println("[]uint8: ", unsafe.Sizeof([]uint8{54}))
}
案例分析
接下来看个例子:
type T1 struct {
i32 int32
b bool
i64 int64
}
type T2 struct {
b bool
i64 int64
i32 int32
}
var data1 T1
var data2 T2
fmt.Println("data1 size: ", unsafe.Sizeof(data1))
fmt.Println("data2 size: ", unsafe.Sizeof(data2))
自己简单算一下,如下是在 64 位机器上的数据类型占用大小
- bool 1B
- int32 4B
- int64 8B
按理说结构体 T1
和 T2
所占用的内存大小应该是一致的,都为 1 + 4 + 8 = 13
个字节,但结果却是这样的
data1 size: 16
data2 size: 24
这就是 go
的编译器做了内存对齐的结果。
下面做简单分析,字母定义为变量占用,X
定义为对齐留空。
结构体 T1
所占用的内存布局如下:
AAAA BXXX CCCC CCCC
结构体 T2
所占用的内存布局如下:
AXXX XXXX BBBB BBBB CCCC XXXX
对齐规则
C
语言可以使用预编译命令来改变对齐系数。
#pragma pack(n)
// n = 1,2,4,8,16
- 数据成员对齐规则:结构(
struct
) 的数据成员,第一个数据成员放在offset
为0
的地方,以后每个数据成员的对齐按照#pragma pack
指定的数值和这个数据成员自身长度中,比较小的那个进行。
个人理解:我可以指定对齐系数,例如指定了 8
,但若是结构体中的成员,如 bool
类型的成员只需要占据一个内存,以小的 bool
的对齐系数为准。
还以上述例子中的 T1
,T2
为例:
func main() {
var data1 T1
var data2 T2
fmt.Println("data1 ", unsafe.Offsetof(data1.i32)) // data1 0
fmt.Println("data2 ", unsafe.Offsetof(data2.i32)) // data2 16
}
- 结构的整体对齐规则:在数据成员完成各自对齐之后,结构本身也要进行对齐,对齐将按照
#pragma pack
指定的数值和结构最大数据成员长度中,比较小的那个进行。 - 结构体变量的首地址是其最长基本类型成员的整数倍。
工具
fieldalignment
fieldalignment 包名/指定文件
//安装
export GOPROXY=https://mirrors.aliyun.com/goproxy/
go get -u golang.org/x/tools/...
//使用
fieldalignment {package}
例如
fieldalignment commonTest/
输出:
F:\GoTest\GoTest\unsafe\SizeOf.go:34:10: struct of size 16 could be 8
structlayout / structlayout-svg
安装
go get -u honnef.co/go/tools
//显示结构体布局
go install honnef.co/go/tools/cmd/structlayout@latest
//重新设计struct字段 减少填充的数量
go install honnef.co/go/tools/cmd/structlayout-optimize@latest
// 用ASCII格式输出
go install honnef.co/go/tools/cmd/structlayout-pretty@latest
//第三方可视化
go install github.com/ajstarks/svgo/structlayout-svg@latest
可以生成如下 svg
图
具体使用
structlayout -json file="./unsafe/SizeOf.go" T1 | structlayout-svg -t "align.T1" > T1.svg
总结
一般项目中很少会去关心结构体的内存对齐,但是内存这玩意儿,还是能省则省吧。
上述所有代码都在我的 Gitee仓库/unsafe包里,该仓库还包含了日常的一些测试代码等,感兴趣的可以看下。