Golang内存对齐的规则及实现

 更新时间:2023年08月08日 15:16:36   作者:~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内存对齐内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言异常处理(Panic和recovering)用法详解

    Go语言异常处理(Panic和recovering)用法详解

    异常处理是程序健壮性的关键,往往开发人员的开发经验的多少从异常部分处理上就能得到体现。Go语言中没有Try Catch Exception机制,但是提供了panic-and-recover机制,本文就来详细讲讲他们的用法
    2022-07-07
  • Go语言封装MinIO相关操作详解

    Go语言封装MinIO相关操作详解

    MinIO 是一个高性能的对象存储服务,兼容 Amazon S3 API,广泛用于存储和管理海量数据,本文将介绍如何用 Go 语言封装一个简单的 MinIO 操作包,需要的可以参考下
    2024-11-11
  • Golang中多个if代码优化小技巧

    Golang中多个if代码优化小技巧

    这篇文章主要为大家详细介绍了Golang中一些常用的if代码优化小技巧,w文中的示例代码简洁易懂,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-10-10
  • Goland使用Go Modules创建/管理项目的操作

    Goland使用Go Modules创建/管理项目的操作

    这篇文章主要介绍了Goland使用Go Modules创建/管理项目的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Golang并发发送HTTP请求的各种方法

    Golang并发发送HTTP请求的各种方法

    在 Golang 领域,并发发送 HTTP 请求是优化 Web 应用程序的一项重要技能,本文探讨了实现此目的的各种方法,从基本的 goroutine 到涉及通道和sync.WaitGroup 的高级技术,需要的朋友可以参考下
    2024-02-02
  • golang中import cycle not allowed解决的一种思路

    golang中import cycle not allowed解决的一种思路

    这篇文章主要给大家介绍了关于golang中import cycle not allowed解决的一种思路,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-08-08
  • go语言实现将重要数据写入图片中

    go语言实现将重要数据写入图片中

    本文给大家分享的是go语言实现将数据的二进制形式写入图像红色通道数据二进制的低位,从而实现将重要数据隐藏,有需要的小伙伴参考下吧。
    2015-03-03
  • golang 两个go程轮流打印一个切片的实现

    golang 两个go程轮流打印一个切片的实现

    这篇文章主要介绍了golang 两个go程轮流打印一个切片的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Go语言中 Channel 详解

    Go语言中 Channel 详解

    Go 语言中的 channel 是实现 goroutine 间无锁通信的关键机制,他使得写多线程并发程序变得简单、灵活、触手可得。下面就个人理解对 channel 使用过程中应该注意的地方进行一个简要的总结。
    2018-10-10
  • Windows上安装Go并配置环境变量(图文步骤)

    Windows上安装Go并配置环境变量(图文步骤)

    开始使用Go创建应用程序之前,需要设置开发环境,本文主要介绍了Windows上安装Go并配置环境变量,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08

最新评论