go 内存对齐

什么是内存对齐

内存对齐对程序员来说,一般是“透明的”,它属于编译器行为,编译器会将程序中的每个“数据单元”安排在适当的位置上。

为什么要内存对齐

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

按理说结构体 T1T2 所占用的内存大小应该是一致的,都为 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
  1. 数据成员对齐规则:结构(struct) 的数据成员,第一个数据成员放在 offset0 的地方,以后每个数据成员的对齐按照 #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
}
  1. 结构的整体对齐规则:在数据成员完成各自对齐之后,结构本身也要进行对齐,对齐将按照 #pragma pack 指定的数值和结构最数据成员长度中,比较的那个进行。
  2. 结构体变量的首地址是其最长基本类型成员的整数倍。

工具

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包里,该仓库还包含了日常的一些测试代码等,感兴趣的可以看下。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值