Go语言结构体中的填充字段:揭秘内存对齐的奥秘

目录

一、内存对齐与填充字段详解

1. 什么是内存对齐?

2. 填充字段有哪些功能?

二、填充字段对性能的影响

三、于知识海洋,共启成长新航


在 Go 语言中,结构体(struct)是一种用户自定义的数据类型,它允许我们把多个不同类型的数据组合在一起,组成一个单一的复合结构。
无论是构建复杂的数据结构,还是实现面向对象编程中的概念,结构体在 Go 语言中都扮演着非常重要的角色!而在某些特定场景下,我们可能会在结构体中定义一些看似无用的字段,代码如下:

type RingBuffer struct {
	_padding0      [8]uint64
	queue          uint64
	_padding1      [8]uint64
	dequeue        uint64
	_padding2      [8]uint64
	mask, disposed uint64
	_padding3      [8]uint64
	nodes          nodes
}

这段代码是我在 Github 上阅读到的,当时我对这个结构体类型的设计感到好奇,不禁思考这样设计的原因是什么?它有哪些优势?在搜索引擎的帮助下,我最终了解到这些字段被称为填充字段(Padding Fields)

一、内存对齐与填充字段详解

1. 什么是内存对齐?

在了解填充字段之前,我们需要先了解一下计算机内存管理的内存对齐概念。

内存对齐是计算机中一种优化数据访问效率的一种方法,简单来说,就是让数据在内存中的存放位置更加符合计算机的处理习惯,从而提高访问速度!计算机的内存是以一个一个字节为单位的,而大多数计算机处理器更喜欢以一块一块(比如 4 字节或 8 字节)来读取数据。内存对齐就是让数据的起始位置正好落在这些大块的起始位置上。

Go 语言在编译结构体时会根据其成员的类型和大小,以及目标平台的内存对齐要求,对它进行内存布局。若结构体成员的大小和对齐要求不一致,Go 编译器可能会在成员之间插入一些额外的空间(这些额外插入的空间就是填充字段),以确保每个成员都按照对齐规则存储。

举个例子,假设有一个结构体如下:

type TestStruct struct {
    a bool
    b int32
    c int8
}

在 64 位系统上,bool 类型通常占用 1 个字节,int32 类型占用 4 个字节,而 int8 类型占用 1 个字节。如果不进行内存对齐,那么这个结构体会占用 6 个字节(1+4+1)。

由于 int32 类型需要 4 字节对齐,那么如果需要内存对齐的话,编译器会在 bool 和 int32 之间,以及 int32 和 int8 之间插入填充字段(分别是 3 字节),使得结构体整体大小变为 12 个字节,如下所示,以满足内存对齐的要求。

| a | 填充 | 填充 | 填充 | b | b | b | b | c | 填充 | 填充 | 填充 |

想象一下,你在一个书架上找书。如果所有的书都按照固定规则排列,比如每个隔板放 4 本书,那么你很快就能找到你需要的书。而如果书是随意放置的,你就得花更多时间去翻找。同样的道理,内存对齐就是让数据按照一定规则排列,这样计算机就能更快地找到它们。

2. 填充字段有哪些功能?

对内存对齐概念有所了解之后,填充字段很明显作用就是能够提高单个字段的访问效率。接下来简单介绍一下填充字段的作用:

  1. 对齐数据:填充字段相当于一个占位符,使得结构体每个成员都能对齐到适当的内存地址。

  2. 缓存行对齐: 现代 CPU 通常会以缓存行的方式读取内存。在大多数 CPU 上,缓存行是 CPU 从内存中读取数据的最小单位,它是一段连续的内存区域,通常为 64 字节。通过使用填充字段,可以确保结构体占据整个缓存行,避免了多个字段分布在不同的缓存行上,从而提高了内存访问效率。

  3. 防止伪共享: 在多核处理器上,多个核心可能同时访问相邻的内存位置,如果不合理地排列数据,就可能导致伪共享问题,降低性能。通过使用填充字段,可以在不同字段之间引入额外的填充,以防止多个核心同时访问相邻内存位置。

  4. 提高内存访问效率:内存对齐的目的就是为了提高 CPU 访问内存的效率。如果数据没有按照对齐规则存储,CPU 可能需要多次内存访问才能读取或写入一个数据项,这会增加访问延迟和降低程序性能。通过插入填充字段,编译器可以确保每个数据项都存储在对齐的内存地址上,从而减少 CPU 访问内存的次数,提高程序的运行效率。

  5. 保证数据一致性:在某些平台上,处理未对齐的数据可能会导致数据读写错误。通过内存对齐和填充字段,编译器能够确保结构体在不同平台具有相同的内存布局,从而避免数据一致性问题和其它潜在问题。

二、填充字段对性能的影响

填充字段虽然有助于优化内存对齐,提高数据访问速度,但有时会造成一定的内存空间浪费,因为为了对齐,可能会在数据之间"留白"。这就像为了让书架整齐,可能在每层放满四本书后会剩下一些空格一样!如果结构体中有大量的填充字段,那么结构体的总大小就会显著增加,从而消耗更多内存

其次,填充字段可能会影响结构体的访问性能。虽然填充字段可以提高单个字段的访问速度,但它们也可能导致缓存未命中的概率增加。当 CPU 从内存中读取数据时,它会将数据加载到缓存中,如果数据在内存中的布局不连续或存在大量的填充字段,那么缓存未命中的概率就会增加,从而导致更长的访问延迟。

为了减少填充字段带来的空间浪费和提高程序的运行效率,在设计结构体时可以采取以下措施:

1.  调整字段顺序:通过重新排列结构体中的字段顺序,可以减少填充字段的数量。将占用空间较大的字段置于结构体的前端,能够有效避免因内存对齐而产生的空间浪费。

2.  使用紧凑的数据类型:在合适的情况下,采用更紧凑的数据类型能够减少内存占用。例如一个字段的取值范围较小时,使用 uint8 或 int8 类型来代替 uint32 或int32 类型会更好一点。

3.  避免不必要的字段:在设计结构体时,应该尽量避免不必要的字段。每个字段不仅会占用内存空间,还可能引入额外的填充字段,增加内存负担。

4.  使用内存池:在某些情况下,使用内存池管理结构体的内存分配能够提升效率。内存池会预先分配一块连续的内存区域,并在需要时从中分配结构体实例。这样做能够减少内存碎片和填充字段的数量,从而提高内存利用率。

通过上述方法,可以在保证程序功能的前提下,有效优化结构体的内存使用并提升整体性能。

三、于知识海洋,共启成长新航

Go语言结构体中的填充字段是内存对齐和性能优化的产物。虽然它们看似无用且增加了内存占用,但实际上对于提高程序性能和保证数据一致性至关重要。通过了解填充字段的产生原因和作用机制,我们可以更好地设计和优化结构体布局,从而提高程序的性能和内存利用率。同时,我们也应该认识到填充字段的局限性,并在实际应用中采取合适的方法来减少其数量和大小。

看到这里,相信你也觉得这篇文章有一定的价值。点赞是一种认可,收藏方便日后回顾,转发则能让这份知识传递得更远。愿你在知乎的世界里不断成长,收获满满的知识与智慧。

⛵智趣学咖资源共享集结号(备用站点)🔥🔥🔥使用 Flowus 构建您的知识平台,助您轻松创作与发布icon-default.png?t=O83Ahttps://lmlat.flowus.cn

​期待你的点赞、收藏与转发,你的每一个举动,都是对我创作的最大鼓励~.~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

所幸你是例外

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值