Golang 的内存对齐
今天又在网上看到了一个以前不知道的概念,内存对齐?从名字就可以看出来,这是一个偏性能优化类的概念。
如果使用了 go-lint,会有这样一个检查项。
|
|
检查是否有更有效的内存布局,其本质就对 Golang 中 Struct 结构体字段的内存对齐进行检查。
前情概要
在分析内存对齐之前,我们先看看几个关于操作系统对内存是如何管理的。
内存是与CPU是程序运行的搭档,内存用于暂存CPU中的运算数据。早期,程序直接运行在物理内存上,直接操作物理内存,但是存在一些问题,比如使用效率低,地址空间不隔离等问题。就出现了虚拟内存这个概念,虚拟内存是程序和物理内存之间的一个中间层,在Liunx操作系统中。虚拟内存被划为用户空间
和 内核空间
,用户只能访问用户空间的虚拟地址,而内核空间只有通过系统调用和外设中断才能访问。
CPU与内存的工作关系是:当执行一个程序时,硬盘上的程序被加载进内存,CPU对内存发起寻址操作,将加载到内存中的指令翻译出来,CPU通过总线寻址、取指令、执行取数据与寄存器交互,然后CPU运算,在输出数据至内存。
而CPU每次寻址能传输的数据大小和CPU位数有关,常见的CPU位数有8位、16位、32位、64位。位数越高每次执行的数据量就越大,性能也就越强。os的位数一般和CPU的位数相匹配, 32位CPU可以寻址4GB的内存空间,
什么是对齐
先看下面的这两个结构体
|
|
在 64位的平台上 T1
占用 24 Bytes,T2
占用16 Bytes ;
在 32位的平台上 T1
占用 16 Bytes,T2
占用 12 Bytes 。
这是为什么呢?
因为编译器使用了内存对齐策略,所以结果就不一样了。而编译器要做对齐主要基于以下2个原因。
平台(移植性)
性能(若访问未对齐的内存,将导致CPU进行2次内存访问,并且要花费额外的时钟中期对齐预算。而本身就对齐的内存仅需要一次访问就可以一次访问完成读取动作,标准的空间换时间的做法)
每个特定平台上的编译器都有自己的默认"对齐系数”,常用平台默认对齐系数如下:
- 32位系统对齐系数是4
- 64位系统对齐系数是8
以上面的 T1
、T2
来说,在 x86_64 平台上,T1
的内存布局为:
而T2的内存布局为
从中可以看到T1存在了许多 padding,显然占据了不少空间,那么也就不难理解为什么改变结构体成员的位置可以达到缩小结构体占用大小的疑问了。
结构体的对齐规则
其实上面的例子已经可以看出对齐规则了,下面再简单总结下:
- 对于结构体的各个成员,第一个成员位于偏移为
0
的位置,结构体第一个成员的偏移量(offset)为0
,以后每个成员相对于结构体首地址的offset
都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。 - 除了结构成员需要对齐,结构本身也需要对齐,结构的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。
举个例子
|
|
- 第一个字段类型是
int32
,对齐值是4,大小为4,所以放在内存布局中的第一位. - 第二个字段类型是
[]int32
,对齐值是8,大小为24
,按照第一条规则,偏移量应该是成员大小24
与对齐值8
中较小那个的整数倍,那么偏移量就是8
,所以4-7
位会由编译进行填充,一般为0
值,也称为空洞,第9
到32
位为第二个字段B
. - 第三个字段类型是
string
,对齐值是8
,大小为16
,所以他的内存偏移值必须是8的倍数,因为user
前两个字段就已经排到了第32
位,所以offset
为32
正好是8
的倍数,不要填充,从32
位到48
位是第三个字段C
. - 第四个字段类型是
bool
,对齐值是1
,大小为1
,所以他的内存偏移值必须是1
的倍数,因为user
前两个字段就已经排到了第48
位,所以下一位的偏移量正好是48
,正好是字段D
的对齐值的倍数,不用填充,可以直接排列到第四个字段,也就是从48
到第49
位是第三个字段D
.
空结构体的字段对齐
Go
语言中空结构体的大小为0
,如果一个结构体中包含空结构体类型的字段时,通常是不需要进行内存对齐的,举个例子:
|
|
但是如果 struct
在结构体的最后一个字段时,需要内存对齐,因为如果有内存指向该字段是,返回的的地址在结构体之外。所以当struct{}
作为结构体成员中最后一个字段时,要填充额外的内存保证安全。
|
|
go-lint 检查
在文章的开头有提到过使用 golangci-lint
命令行工具进行内存对齐检查。首先我们打印一下 linters
中和 mem
有关的选项,从后面的说明已经其功能了。
|
|
还是上面最开始提到的例子。
|
|
运行lint检查命令。
|
|
可以看到 lint
报了一个 WARN ,T1
这个结构体的字节数可以更少的。
参考: