golang 内存对齐的实现

 更新时间:2024年08月14日 11:00:04   作者:m旧裤子  
在代码编译阶段,编译器会对数据的存储布局进行对齐优化,本文主要介绍了golang 内存对齐的实现,具有一定的参考价值,感兴趣的可以了解一下

什么是内存对齐

在访问特定类型变量的时候通常在特定的内存地址访问,这就需要对这些数据在内存中存放的位置有限制,各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

内存对齐是编译器的管辖范围。表现为:编译器为程序中的每个“数据单元”安排在适当的位置上。

为什么需要内存对齐

  • 有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时,将分配的内存进行对齐,这就具有平台可以移植性了。

  • CPU 访问内存时并不是逐个字节访问,而是以字长(word size)为单位访问,例如 32位的CPU 字长是4字节,64位的是8字节。如果变量的地址没有对齐,可能需要多次访问才能完整读取到变量内容,而对齐后可能就只需要一次内存访问,因此内存对齐可以减少CPU访问内存的次数,加大CPU访问内存的吞吐量。

假设每次访问的步长为4个字节,如果未经过内存对齐,获取b的数据需要进行两次内存访问,最后再进行数据整理得到b的完整数据:

在这里插入图片描述

如果经过内存对齐,一次内存访问就能得到b的完整数据,减少了一次内存访问:

在这里插入图片描述

golang中unsafe.AlignOf()函数

unsafe.AlignOf(x) 方法的返回值是 m,当变量进行内存对齐时,需要保证分配到 x 的内存地址能够整除 m。因此可以通过这个方法,确定变量x 在内存对齐时的地址:

  • 对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
  • 对于 struct 结构体类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
  • 对于 array 数组类型的变量x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。

对于系统内置基础类型变量 x ,unsafe.Alignof(x) 的返回值就是 min(字长/8,unsafe.Sizeof(x)),即计算机字长与类型占用内存的较小值:

func main() {
  fmt.Println(unsafe.Alignof(int(1))) // 1 -- min(8,1)
  fmt.Println(unsafe.Alignof(int32(1))) // 4 -- min (8,4)
  fmt.Println(unsafe.Alignof(int64(1))) // 8 -- min (8,8)
  fmt.Println(unsafe.Alignof(complex128(1))) // 8 -- min(8,16)
}  

内存对齐规则

  • 成员对齐规则

针对一个基础类型变量,如果 unsafe.AlignOf() 返回的值是 m,那么该变量的地址需要 被m整除 (如果当前地址不能整除,填充空白字节,直至可以整除)。

  • 整体对齐规则

针对一个结构体,如果 unsafe.AlignOf() 返回值是 m,需要保证该结构体整体内存占用是 m的整数倍,如果当前不是整数倍,需要在后面填充空白字节。

通过内存对齐后,就可以在保证在访问一个变量地址时:

  • 如果该变量占用内存小于字长:保证一次访问就能得到数据;
  • 如果该变量占用内存大于字长:保证第一次内存访问的首地址,是该变量的首地址。

eg:

type A struct {
	a int32
	b int64
	c int32
}

func main() {
	fmt.Println(unsafe.Sizeof(A{1, 1, 1}))  // 24
}

第一个字段是 int32 类型,unsafe.Sizeof(int32(1))=4,内存占用为4个字节,同时unsafe.Alignof(int32(1)) = 4,内存对齐需保证变量首地址可以被4整除,我们假设地址从0开始,0可以被4整除:

在这里插入图片描述

2. 第二个字段是 int64 类型,unsafe.Sizeof(int64(1)) = 8,内存占用为 8 个字节,同unsafe.Alignof(int64(1)) = 8,需保证变量放置首地址可以被8整除,当前地址为4,距离4最近的且可以被8整除的地址为8,因此需要添加四个空白字节,从8开始放置:

S

第三个字段是 int32 类型,unsafe.Sizeof(int32(1))=4,内存占用为4个字节,同时unsafe.Alignof(int32(1)) = 4,内存对齐需保证变量首地址可以被4整除,当前地址为16,16可以被4整除:

在这里插入图片描述

所有成员对齐都已经完成,现在我们需要看一下整体对齐规则:unsafe.Alignof(A{}) = 8,即三个变量成员的最大值,内存对齐需要保证该结构体的内存占用是 8 的整数倍,当前内存占用是 20个字节,因此需要再补充4个字节:

在这里插入图片描述

最终该结构体的内存占用为 24字节。

type B struct {
	a int32
	b int32
	c int64
}

func main() {
	fmt.Println(unsafe.Sizeof(B{1, 1, 1}))  // 16
}

第一个字段是 int32 类型,unsafe.Sizeof(int32(1))=4,内存占用为4个字节,同时unsafe.Alignof(int32(1)) = 4,内存对齐需保证变量首地址可以被4整除,我们假设地址从0开始,0可以被4整除:

在这里插入图片描述

第二个字段是 int32 类型,unsafe.Sizeof(int32(1))=4,内存占用为4个字节,同时unsafe.Alignof(int32(1)) = 4,内存对齐需保证变量首地址可以被4整除,当前地址为4,4可以被4整除:

在这里插入图片描述

第三个字段是 int64 类型,unsafe.Sizeof(int64(1))=8,内存占用为8个字节,同时unsafe.Alignof(int64(1)) = 8,内存对齐需保证变量首地址可以被8整除,当前地址为8,8可以被8整除:

在这里插入图片描述

所有成员对齐都已经完成,现在我们需要看一下整体对齐规则:unsafe.Alignof(B{}) = 8,即三个变量成员的最大值,内存对齐需要保证该结构体的内存占用是 8 的整数倍,当前内存占用是 16个字节,已经符合规则,最终该结构体的内存占用为 16个字节。

空结构体对齐规则

如果空结构体作为结构体的内置字段:当变量位于结构体的前面和中间时,不会占用内存;当该变量位于结构体的末尾位置时,需要进行内存对齐,内存占用大小和前一个变量的大小保持一致。

type C struct {
	a struct{}
	b int64
	c int64
}

type D struct {
	a int64
	b struct{}
	c int64
}

type E struct {
	a int64
	b int64
	c struct{}
}

type F struct {
	a int32
	b int32
	c struct{}
}

func main() {
	fmt.Println(unsafe.Sizeof(C{})) // 16
	fmt.Println(unsafe.Sizeof(D{})) // 16
	fmt.Println(unsafe.Sizeof(E{})) // 24
    fmt.Println(unsafe.Sizeof(F{})) // 12
}

参考:https://juejin.cn/post/7077833959047954463

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

相关文章

  • Golang的os标准库中常用函数的整理介绍

    Golang的os标准库中常用函数的整理介绍

    这篇文章主要介绍了Go语言的os标准库中常用函数,主要用来实现与操作系统的交互功能,需要的朋友可以参考下
    2015-10-10
  • go语言版的ip2long函数实例

    go语言版的ip2long函数实例

    这篇文章主要介绍了go语言版的ip2long函数,实例分析了Go语言实现的ip2long函数技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • 通过Golang实现linux命令ls命令(命令行工具构建)

    通过Golang实现linux命令ls命令(命令行工具构建)

    这篇文章主要为大家详细介绍了如何通过Golang实现一个linux命令ls命令(命令行工具构建),文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下
    2023-01-01
  • 一文带你深入了解Golang中的参数传递机制

    一文带你深入了解Golang中的参数传递机制

    值传递和引用传递是编程语言中两种主要的参数传递方式,决定了函数调用过程中实参如何影响形参以及函数内部对形参的修改是否会影响到原始实参,下面就跟随小编一起深入了解下golang中参数传递机制吧
    2024-01-01
  • Go语言中通过Lua脚本操作Redis的方法

    Go语言中通过Lua脚本操作Redis的方法

    这篇文章主要给大家介绍了关于Go语言中通过Lua脚本操作Redis的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2018-01-01
  • Golang中的包及包管理工具go mod详解

    Golang中的包及包管理工具go mod详解

    这篇文章主要介绍了Golang中的包及包管理工具go mod,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • Go语言大揭秘:适用于哪些类型的项目开发?

    Go语言大揭秘:适用于哪些类型的项目开发?

    想知道Go编程语言适合开发哪些类型的项目吗?无论是网络服务、分布式系统还是嵌入式设备,Go都能轻松应对,本文将带你了解Go在各种场景下的应用,让你更好地选择和使用Go进行开发,需要的朋友可以参考下
    2024-01-01
  • 如何使用大学教育邮箱下载golang等软件(推荐)

    如何使用大学教育邮箱下载golang等软件(推荐)

    这篇文章主要介绍了如何使用大学教育邮箱下载goland等软件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 初探GO中unsafe包的使用

    初探GO中unsafe包的使用

    unsafe是Go语言标准库中的一个包,提供了一些不安全的编程操作,本文将深入探讨Go语言中的unsafe包,介绍它的使用方法和注意事项,感兴趣的可以了解下
    2023-08-08
  • Goland配置leetcode的实现示例

    Goland配置leetcode的实现示例

    本文主要介绍了Goland配置leetcode的实现示例,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06

最新评论