Golang内存对齐

什么是内存对齐?

  编译器会将数据按照特定的规则,把数据安排到合适的存储地址上,并占用合适的地址长度

为什么要内存对齐

  保证程序顺利高效的运行,可以让CPU快速从内存中存取到字段,避免资源浪费

内存对齐规则

     1、起始的存储地址 必须是 内存对齐边界 的倍数。
     2、整体占用字节数 必须是 内存对齐边界 的倍数。

Tip:先声明两个概念 ↓ ↓
      内存对齐边界:结构体所有元素中,哪个元素占用的字节数大,那么这个元素占用的字节数就是内存对齐边界
      对齐边界:结构体中每个元素自己占用的字节数

通过下边的示例来理解内存对齐的规则

     首先我们定义一个结构体:

type S struct {
   A uint8     // byte:1
   B int32     // byte:4
   C int16     // byte:2
   D int64     // byte:8
   E [2]string // byte总和:32 -->string类型数组,总共2个元素,每个元素包含2部分内容:
   			   // 内容1:ptr,指向存放数据的地址,byte:8:  内容2:len,标识字符串长度的整数值,byte:8
   F struct{}  // zero size field
}

步骤一:确定内存对齐边界

    统计出结构体中所有的元素分别占用的字节数,占用最大的字节数就是内存对齐边界,在这个示例中的内存对齐边界就是 8
    Tip:如果不知道怎样确定内存对齐边界,可以使用unsafe.Alignof()函数打印每个元素的对齐系数,打印中的最大值就是内存对齐边界:

func main() {
	fmt.Println(unsafe.Alignof(S{}.A))   // output: 1
	fmt.Println(unsafe.Alignof(S{}.B))  // output: 4
	fmt.Println(unsafe.Alignof(S{}.C))  // output: 2
	fmt.Println(unsafe.Alignof(S{}.D))  // output: 8
	fmt.Println(unsafe.Alignof(S{}.E))  // output: 8
	fmt.Println(unsafe.Alignof(S{}.F))  // output: 1
	
	fmt.Println(unsafe.Sizeof(S{}))		//可以直接打印出结构体所占用的字节数
}

步骤二:确定起始存储地址
    内存对齐规则第一条:起始的存储地址 必须是 内存对齐边界 的倍数。也就是(起始地址addr)%(内存对齐边界)=0,在我们示例中起始地址就是addr%8=0,我们从0地址开始,为了方便看,我们先用图来展示地址存储分布图

在这里插入图片描述

起始地址为0,0%8=0,符合条件,那我们就从0开始存储数据

其中元素A占用1个字节,并且0%1=0,符合条件,第0个地址分配给A;
       元素B占用4个字节,从1个地址分配的话,1%4≠0,只有4%4=0,所以把第4-7个地址分配给B;
       以此类推······ 具体分配如下

  元素A:1个字节,占用第0个相对地址空间,      0%1 =0(起始地址为0,内存边界为1)
  元素B:4个字节,占用第4-7个相对地址空间,    4%4 =0(起始地址为4,内存边界为4)
  元素C:2个字节,占用第8-9个相对地址空间,    8%2 =0(起始地址为8,内存边界为2)
  元素D:8个字节,占用第16-23个相对地址空间,  16%8=0(起始地址为16,内存边界为8)
  元素E:8*2*2个字节,占用第24-31和32-39和40-47和48-55个相对地址空间,24%8=0...(起始地址为24,内存边界为8...)
  元素F:zero size field,占用第56个相对地址空间,	 56%1=0 (起始地址为56,内存边界为1)

       最终内存在第56个相对地址空间分配完毕,但是我们的地址空间是从0开始计算的,所以目前来看结构体总共占用57个字节!

步骤三:确定结构体占用字节数

       我们在步骤二中排列出了结构体中元素的存放地址,整体元素占用57个字节,但是到这里还不算完事儿,因为我们还没有执行第二条的内存对齐规则–>整体占用字节数 必须是 内存对齐边界 的倍数。我们的内存对齐边界为8,而57不是8的倍数,所以我们需要扩张字节空间到8的倍数,延伸到64,也就是扩张到到图中的第63个相对地址空间。这个结构体占用的字节数为64字节!

内存空间优化

       通过上边的示例与图表我们不难看出,其中还有好多个地址空间被浪费掉了,这些没被利用的地址空间,go语言会进行padding操作来对这些空间进行填充,使这些空间变成合法的内存空间。

       我们再思考一下,如何才能减少地址空间的浪费呢?能不能通过重新排列元素的位置来合理的分配地址空间呢?
答案当然是肯定的,我们可以通过合理排列元素的定义顺序来减少地址空间的浪费。

       我们先看结论,下边是重新排列后的结构体:

type S1 struct {
	A uint8
	F struct{}
	C int16
	B int32
	D int64
	E [2]string
}

       再看一下重新分配地址空间的图标:

在这里插入图片描述

       起始地址为0,0%8=0,符合条件,我们就从0开始存储数据

其中元素A占用1个字节,并且0%1=0,符合条件,第0个地址分配给A;
       元素F占用1个字节,1%1=0,符合条件,将第1个地址分配给B;
       以此类推······ 具体分配如下

  元素A:1个字节,占用第0个相对地址空间,      	0%1 =0(起始地址为0,内存边界为1)
  元素F:zero size field,占用第1个相对地址空间,1%1 =0(起始地址为1,内存边界为1)
  元素C:2个字节,占用第2-3个相对地址空间,	    2%2 =0(起始地址为2,内存边界为2)
  元素B:4个字节,占用第4-7个相对地址空间,	    4%4=0(起始地址为4,内存边界为4)
  元素D:8个字节,占用第8-15个相对地址空间,		8%8=0(起始地址为8,内存边界为8)
  元素E:8*2*2个字节,占用第16-23和24-31和32-39和40-47个相对地址空间,16%8=0... (起始地址为16,内存边界为8...)

       最终内存在第47个相对地址空间分配完毕,但是我们的地址空间是从0开始计算的,所以目前来看结构体总共占用48个字节!并且48还是内存对齐边界值8的整数倍,所以结构体最终占用48个字节!

       我们可以很明显的看出来,在我们改变元素的定义顺序后,占用的字节空间从64字节减少到了48字节,内存空间得到了充分的优化!!!这也是一个结论所在,我们在结构体定义变量的时候,尽量将相同类型的变量定义在一起,将占用字节较少的变量类型放在一块。这也只是我个人的一个结论,其中内容有问题的话,欢迎大家进行指正!感谢您的阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值