go的内存对齐

482 阅读2分钟

为什么要内存对齐?

先看个问题:

carbon (6).png 这个最终会输出多少?是一个1byte(1字节) + int32(4) = 5个字节吗?答案是8。

存取粒度:
首先内存是以字节为单位的,当然处理器不会按照一个字节一个字节来读取数据的,这样效率太低。处理器为了提高读取的效率,可以每次读取一批字节, 这样的一批可以2、4、8、16等等(2^n),这个叫内存存取粒度。假设现在存取粒度是8,那么现在有一个字节,它存在第二个位置上,cpu会把8个都读进来, 然后去掉第一个和后面6个,剩下的就是目标字节。

image.png

不对齐有什么问题?
假设现在存取粒度是8,且有两个数据各占5个字节。

  • 不对齐 image.png 可以发现第二个数据分布在第一块内存块(0-8)和第二块内存块(8-16)中,那么读取第二块数据的时候,就要读两次,然后剔除第一块内存块的前5位,剔除第二个内存块的后6位,然后组装。
  • 对齐 image.png cpu可以直接切到第二个内存块,一次性读取8个字节,取前5个,剔除后面3个,结束,只需要读取一次。

go内存对齐

go的对齐系数可以通过unsafe.Alignof获取,占用长度可以用unsafe.Sizeof获取。

  • unsafe.Alignof 官方文档的描述:
  1. For a variable x of any type: unsafe.Alignof(x) is at least 1.
  2. 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.
  3. 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. 任何类型对齐长度至少为1
  2. 对于struct,对齐长度为内部最大对其长度的那个成员
  3. 对于数组类型,对齐长度是元素类型的长度(比如[2]int8就是int8的长度=1)
  • unsafe.Sizeof 获取类型对齐长度:

image.png

对齐优化

carbon (8).png

image.png

  1. 根据unsafe.Alignof 的规则2,得出struct的对齐长度为8
  2. a占用一个字节,对齐长度是8,且a的下一个元素是c,c就占8个字节,所以第一块浪费7个字节
  3. c占用8个字节,占满第二块
  4. b占4字节,第三块浪费4字节 总共占用24字节。

我们换下b和c的顺序:

carbon (9).png

image.png

  1. 根据unsafe.Alignof 的规则2,得出struct的对齐长度为8
  2. a占用一个字节,对齐长度是8,且a的下一个元素是b,b占用4个字节,a和b都在第一块,第一块剩余3个字节
  3. c占8字节,第一块剩余的3个不够,占满第二块 总共占用16字节。

对于struct类型,合理的成员顺序可以减少内存占用。