golang内存对齐的概念及案例详解

 更新时间:2022年02月02日 09:10:52   作者:Dawnlight-_-  
为保证程序顺利高效的运行,编译器会把各种类型的数据安排到合适的地址,并占用合适的长度,这就是内存对齐。本文重点给大家介绍golang内存对齐的概念及案例详解,感兴趣的朋友一起看看吧

什么是内存对齐

为保证程序顺利高效的运行,编译器会把各种类型的数据安排到合适的地址,并占用合适的长度,这就是内存对齐。

每种类型的对齐值就是它的对齐边界,内存对齐要求数据存储地址以及占用的字节数都要是它的对齐边界的倍数。所以下述的int32要错开两个字节,从4开始存,却不能紧接着从2开始。

也可以这样解释:

CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度)。

如果不进行内存对齐

比如我们想从地址1开始读8字节的数据:

CPU会分两次读:

  • 第一次从 0 - 7 但只取后 7 字节。
  • 第二次从 8 - 15 但只取第 1 字节。

分两次读,这样势必会对性能造成影响。

为什么要内存对齐

原因主要有两点:

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

对齐边界

那该怎么确定每种数据的对齐边界呢?这和平台有关,go语言支持这些平台:

可以看到常见的32位平台,指针宽度和寄存器宽度都是4字节,64位平台上都是8字节。而被go语言称为寄存器宽度的这个值,就可以理解为机器字长,也是平台对应的最大对齐边界。

而数据类型的对齐边界,是取类型大小与平台最大对齐边界中较小的那个。不过要注意,同一个类型在不同平台上,大小可能不同,对齐边界也可能不同。

为什么不统一使用平台最大对齐边界呢?或者统一按各类型大小来对齐呢?

我们来试一下,假设目前是64位平台,最大对齐边界为8字节。int8只有1字节,按照1字节对齐的话,它可以放在任何位置,因为总能通过一次读取把它完整拿出来。如果统一对齐到8字节,虽然同样只要读取一次,但每个int8的变量都要浪费7字节,所以对齐到1。

int16占2字节,按照2字节对齐,可以从这些地址开始存,而且能保证只用读取一次。

如果按1字节对齐就可能存成这样,那就要读取两次再截取拼接,会影响性能。

如果按8字节对齐,会与int8一样浪费内存,所以对齐到2。

这是小于最大对齐边界的情况,再来看看大于的情况。

假设要在32位的平台下存储一个int64类型的数据,在0和1位置被占用的情况下,就要从位置8开始存。而如果对齐到4,就可以从位置4开始,内存浪费更少,所以选择对齐到4。

因此类型对齐边界会这样选择,依然是为了减少浪费提升性能。

GO 计算对齐边界函数

在go语言中可以调用 unsafe.Alignof 来返回相应类型的对齐边界:

func main() {
	fmt.Printf("bool align: %d\n", unsafe.Alignof(bool(true)))
	fmt.Printf("int32 align: %d\n", unsafe.Alignof(int32(0)))
	fmt.Printf("int8 align: %d\n", unsafe.Alignof(int8(0)))
	fmt.Printf("int64 align: %d\n", unsafe.Alignof(int64(0)))
	fmt.Printf("byte align: %d\n", unsafe.Alignof(byte(0)))
	fmt.Printf("string align: %d\n", unsafe.Alignof("EDDYCJY"))
	fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
}

运行结果:

bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
string align: 8
map align: 8

确定结构体的对齐边界

对结构体而言,首先要确定每个成员的对齐边界,然后取其中最大的,这就是这个结构体的对齐边界。

然后来存储这个结构体变量:

内存对齐要求一:

  • 存储这个结构体的起始地址,是对齐边界的倍数。

​假设从0开始存,结构体的每个成员在存储时,都要把这个起始地址当作地址0,然后再用相对地址来决定自己该放在哪里。

内存对齐要求2:

  • 结构体整体占用字节数需要是类型对齐边界的倍数,不够的话要往后扩张一下。

​所以最终上述结构体类型的大小就是24字节。

案例

type Part1 struct {
	a bool
	b int32
	c int8
	d int64
	e byte
}
type Part2 struct {
	a bool
	c int8
	e byte
	b int32 // 4个字节
	d int64
}

分别求以上两个结构体占用的字节:

fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))

这里我们直接调用函数求得:

part1 size: 32, align: 8
part2 size: 16, align: 8

原因请读者来思考。

参考资料:
https://blog.csdn.net/u010853261/article/details/102557188
https://www.bilibili.com/video/BV1Ja4y1i7AF?from=search&seid=16213689667007976568&spm_id_from=333.337.0.0

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

相关文章

  • golang内存对齐详解

    golang内存对齐详解

    在golang中,每一种数据类型都有其对应的数据类型大小,也就是占用了多少内存空间,我们可以通过unsafe.Sizeof函数,来确定一个变量占用的内存字节数,本文将详细给大家介绍golang内存对齐,需要的朋友可以参考下
    2023-10-10
  • Go gin框架处理panic的方法详解

    Go gin框架处理panic的方法详解

    本文我们介绍下recover在gin框架中的应用, 首先,在golang中,如果在子协程中遇到了panic,那么主协程也会被终止,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • golang中tar压缩和解压文件详情

    golang中tar压缩和解压文件详情

    这篇文章主要给大家介绍golang中tar压缩和解压文件,文章以查看官方文档自带的给大家演习一下golang的archive/tar压缩和解压功能,需要的朋友可以参考一下
    2021-11-11
  • 详解Golang并发操作中常见的死锁情形

    详解Golang并发操作中常见的死锁情形

    在Go的协程里面死锁通常就是永久阻塞了,本文主要介绍了Golang并发操作中常见的死锁情形,具有一定的参考价值,感兴趣的可以了解一下
    2021-09-09
  • 解决golang 反射interface{}做零值判断的一个重大坑

    解决golang 反射interface{}做零值判断的一个重大坑

    这篇文章主要介绍了解决golang 反射interface{}做零值判断的一个重大坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Golang拾遗之实现一个不可复制类型详解

    Golang拾遗之实现一个不可复制类型详解

    在这篇文章中我们将实现一个无法被复制的类型,顺便加深对引用类型、值传递以及指针的理解。文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-02-02
  • golang图片处理库image基本操作

    golang图片处理库image基本操作

    这篇文章主要介绍了golang图片处理库image简介,主要包括图片的基本读取与保存及图片的修改,本文通过通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • 使用go实现一个超级mini的消息队列的示例代码

    使用go实现一个超级mini的消息队列的示例代码

    本文主要介绍了使用go实现一个超级mini的消息队列的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • go-micro集成RabbitMQ实战和原理详解

    go-micro集成RabbitMQ实战和原理详解

    本文主要介绍go-micro使用RabbitMQ收发数据的方法和原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • VSCode配置Go插件和第三方拓展包的详细教程

    VSCode配置Go插件和第三方拓展包的详细教程

    这篇文章主要介绍了VSCode配置Go插件和第三方拓展包的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05

最新评论