Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang内存对齐

Golang内存对齐的规则及实现

作者:~kiss~

本文介绍了Golang内存对齐的规则及实现,通过合理的内存对齐,可以提高程序的执行效率和性能,通过对本文的阅读,读者可以更好地理解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字节,内存空间得到了充分的优化!!!这也是一个结论所在,我们在结构体定义变量的时候,尽量将相同类型的变量定义在一起,将占用字节较少的变量类型放在一块。

到此这篇关于Golang内存对齐的规则及实现的文章就介绍到这了,更多相关Golang内存对齐内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

阅读全文