为什么要内存对齐?
先看个问题:
这个最终会输出多少?是一个1byte(1字节) + int32(4) = 5个字节吗?答案是8。
存取粒度:
首先内存是以字节为单位的,当然处理器不会按照一个字节一个字节来读取数据的,这样效率太低。处理器为了提高读取的效率,可以每次读取一批字节,
这样的一批可以2、4、8、16等等(2^n),这个叫内存存取粒度。假设现在存取粒度是8,那么现在有一个字节,它存在第二个位置上,cpu会把8个都读进来,
然后去掉第一个和后面6个,剩下的就是目标字节。
不对齐有什么问题?
假设现在存取粒度是8,且有两个数据各占5个字节。
- 不对齐
可以发现第二个数据分布在第一块内存块(0-8)和第二块内存块(8-16)中,那么读取第二块数据的时候,就要读两次,然后剔除第一块内存块的前5位,剔除第二个内存块的后6位,然后组装。
- 对齐
cpu可以直接切到第二个内存块,一次性读取8个字节,取前5个,剔除后面3个,结束,只需要读取一次。
go内存对齐
go的对齐系数可以通过unsafe.Alignof获取,占用长度可以用unsafe.Sizeof获取。
- unsafe.Alignof 官方文档的描述:
- For a variable x of any type: unsafe.Alignof(x) is at least 1.
- For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1.
- For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array’s element type
- 任何类型对齐长度至少为1
- 对于struct,对齐长度为内部最大对其长度的那个成员
- 对于数组类型,对齐长度是元素类型的长度(比如[2]int8就是int8的长度=1)
- unsafe.Sizeof 获取类型对齐长度:
对齐优化:
- 根据unsafe.Alignof 的规则2,得出struct的对齐长度为8
- a占用一个字节,对齐长度是8,且a的下一个元素是c,c就占8个字节,所以第一块浪费7个字节
- c占用8个字节,占满第二块
- b占4字节,第三块浪费4字节 总共占用24字节。
我们换下b和c的顺序:
- 根据unsafe.Alignof 的规则2,得出struct的对齐长度为8
- a占用一个字节,对齐长度是8,且a的下一个元素是b,b占用4个字节,a和b都在第一块,第一块剩余3个字节
- c占8字节,第一块剩余的3个不够,占满第二块 总共占用16字节。
对于struct类型,合理的成员顺序可以减少内存占用。