如何让你的结构体更高效

如何,结构,高效 · 浏览次数 : 58

小编点评

**内存对齐** 在Go语言中,`unsafe.Sizeof()`函数可用于查看变量所占的内存大小。在64位机器上执行的代码运行结果如下: ```go fmt.Println(unsafe.Sizeof(true)) // 1 fmt.Println(unsafe.Sizeof(int8(1))) // 1 fmt.Println(unsafe.Sizeof(int(1))) // 8 fmt.Println(unsafe.Sizeof(int32(1))) // 4 fmt.Println(unsafe.Sizeof(int64(1))) // 8 fmt.Println(unsafe.Sizeof(float32(1.0))) // 4 fmt.Println(unsafe.Sizeof(float64(1.0))) // 8 ``` 从输出中可以看出,`true`、`int8(1)`、`int(1)`、`int32(1)`、`int64(1)`和`float32(1.0)`的内存大小分别为1、1、8、4、8、4和8。 这表明,`true`占用1字节,`int8(1)`占用1字节,`int(1)`占用1字节,`int32(1)`占用4字节,`int64(1)`占用8字节,`float32(1.0)`占用4字节,`float64(1.0)`占用8字节。 **内存对齐规则** 内存对齐是指在计算机内存中存储数据时按照特定规则对齐到内存地址的过程。内存对齐可以提高内存访问效率和系统性能。 在Go中,内存对齐通常通过数据类型对齐来实现。数据类型在内存中占用的字节数可以是不同的,例如,整数可能占用2字节、4字节或8字节,而字符可能只占用1字节。 **fieldalignment 工具** `fieldalignment` 工具可以帮助你解决内存对齐问题。使用 `fieldalignment` 工具可以设置数据类型对齐属性,并确保数据按照正确的顺序和对齐要求进行存储。 **结论** 内存对齐是优化计算机内存访问性能和性能的关键技术。在Go中,内存对齐通过数据类型对齐来实现。可以使用 `fieldalignment` 工具来解决内存对齐问题。

正文

文中所涉及到的代码运行结果均是在64位机器上执行得到的.

基础知识回顾

在Go中,我们可以使用unsafe.Sizeof(x)来查看变量所占的内存大小。以下是Go内置的数据类型占用的内存大小:

类型 内存大小(字节数)
bool 1
int8/uint8 1
int/uint 8
int32/uint32 4
int64/uint64 8
float32 4
float64 8
complex64 8
complex128 16
指针类型:*T, map,func,chan 8
string 16
interface 16
[]T 24

func main() {
	fmt.Println(unsafe.Sizeof(true))     // 1
	fmt.Println(unsafe.Sizeof(int8(1)))  // 1
	fmt.Println(unsafe.Sizeof(int(1)))   // 8
	fmt.Println(unsafe.Sizeof(int32(1))) // 4
	fmt.Println(unsafe.Sizeof(int64(1))) // 8

	fmt.Println(unsafe.Sizeof(float32(1.0))) // 4
	fmt.Println(unsafe.Sizeof(float64(1.0))) // 8

	a := int(1)
	fmt.Println(unsafe.Sizeof(&a)) // 8

	s := "1234"
	fmt.Println(unsafe.Sizeof(s)) //16

	var b interface{}
	fmt.Println(unsafe.Sizeof(b)) //16

	fmt.Println(unsafe.Sizeof([]string{})) // 24
	fmt.Println(unsafe.Sizeof([]int{}))    // 24
}

简单示例

对于一个结构体,其占用的内存大小应该是其内部多个基础类型占用内存大小之和。但实际情况并非如此,甚至字段顺序不同,结构体的大小也不同:

type Example1 struct {
	a int32 // 4
	b int32 // 4
	c int64 // 8
}

type Example2 struct {
	a int32 // 4
	c int64 // 8
	b int32 // 4
}

type Example3 struct {
	a bool   // 1
	b int8   // 1
	c string // 16
}

func main() {
	fmt.Println(unsafe.Sizeof(Example1{})) // 16
	fmt.Println(unsafe.Sizeof(Example2{})) // 24
    fmt.Println(unsafe.Sizeof(Example3{})) // 24,并不是1+1+16=18
}

为什么会出现上面的情况呢?这就引出了本文的重点:内存对齐

内存对齐

内存对齐(Memory Alignment)是指数据在计算机内存中存储时按照特定规则对齐到内存地址的过程。内存对齐是由计算机硬件和操作系统所决定的,它可以提高内存访问效率和系统性能。

在计算机体系结构中,内存是以字节(byte)为单位进行访问的。数据类型在内存中占用的字节数可以是不同的,例如,整数可能占用2字节、4字节或8字节,而字符可能只占用1字节。

内存对齐的规则要求变量的地址必须是其数据类型字节数的整数倍。例如,如果一个变量的数据类型是4字节(32位),那么它的起始地址必须是4的倍数。

内存对齐的主要目的是优化计算机的内存访问性能。当数据按照对齐要求存储在内存中时,读取和写入操作可以更高效地进行。如果数据没有按照对齐要求存储,计算机可能需要进行多次内存读取操作来获取完整的数据,这会增加访问延迟和降低系统性能。

在编程中,特别是在使用结构体和类的语言中,内存对齐是一个重要的概念。编译器会根据数据类型的对齐要求自动进行内存对齐操作,以确保数据存储的正确性和性能优化。但在某些情况下,可以通过显式地设置对齐属性来控制数据的对齐方式,以满足特定的需求。

需要注意的是,不同的硬件平台和操作系统可能具有不同的内存对齐规则和要求。因此,在开发跨平台应用程序时,应当考虑到这些差异并遵循适当的内存对齐规则。

为什么需要对齐内存

内存对齐是为了提高计算机系统的内存访问效率和性能而存在的。以下是几个需要内存对齐的原因:

  1. 硬件要求:许多计算机硬件和体系结构对内存访问有特定的对齐要求。如果数据没有按照硬件要求进行对齐,可能会导致访问错误、异常或性能下降。通过满足硬件对齐要求,可以确保数据能够按照有效的方式访问,提高系统的稳定性和性能。
  2. 内存访问效率:当数据按照对齐要求存储在内存中时,计算机系统可以更高效地访问这些数据。对齐数据可以减少或避免多次内存访问,提高数据的读取和写入速度。这对于大量的数据操作和高性能计算非常重要。
  3. 缓存性能:现代计算机系统中通常有多级缓存,而缓存的访问是以特定块的方式进行的。内存对齐可以确保数据按照缓存块的大小对齐,使得数据能够更好地利用缓存,减少缓存未命中和读取延迟,提高缓存性能。
  4. 结构体和类的内存布局:结构体和类通常包含多个成员变量,这些变量按照一定的顺序存储在内存中。内存对齐确保结构体和类的成员变量按照正确的顺序和对齐要求进行存储,避免内存空洞和访问错误,保证数据的正确性和一致性。

总之,内存对齐是为了满足硬件要求、提高内存访问效率和性能而引入的机制。通过合理地进行内存对齐,可以提高系统的稳定性、性能和响应速度,并避免潜在的内存访问问题。

如何对齐内存

Go团队开发了一款名为fieldalignment的工具可以帮助我们解决内存对齐的问题。

使用下面的命令安装fieldalignment工具:

$ go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest

还是以上面的代码为例,可以执行下面的命令:

$ fieldalignment main.go  
main.go:14:15: struct of size 24 could be 16  
main.go:20:15: struct with 16 pointer bytes could be 8

也可以使用--fix参数直接修改代码:

$ fieldalignment --fix main.go

修改后的内容如下:

type Example1 struct {
	a int32 // 4
	b int32 // 4
	c int64 // 8
}

type Example2 struct {
	c int64
	a int32
	b int32
}

type Example3 struct {
	c string
	a bool
	b int8
}

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


与如何让你的结构体更高效相似的内容:

如何让你的结构体更高效

> 文中所涉及到的代码运行结果均是在64位机器上执行得到的. ## 基础知识回顾 在Go中,我们可以使用`unsafe.Sizeof(x)`来查看变量所占的内存大小。以下是Go内置的数据类型占用的内存大小: | 类型 | 内存大小(字节数) | | : | : | | bool | 1 | | in

如何让程序更健壮「GitHub 热点速览」

对于 ML 模型训练而言,好的数据集能让结果更健壮,cleanlab 是一个降低数据噪音,及时帮你修正数据集错误的工具。好的工具能让你的结果更完美。同样的,RedTeam-Tools 提高了渗透测试的能力,也间接地让你的安全系统更牢固。DocsGPT 一看便知它是个 Docs + GPT 的结合体,

驱动开发:应用DeviceIoContro模板精讲

在笔者上一篇文章`《驱动开发:应用DeviceIoContro开发模板》`简单为大家介绍了如何使用`DeviceIoContro`模板快速创建一个驱动开发通信案例,但是该案例过于简单也无法独立加载运行,本章将继续延申这个知识点,通过封装一套标准通用模板来实现驱动通信中的常用传递方式,这其中包括了如何传递字符串,传递整数,传递数组,传递结构体等方法。可以说如果你能掌握本章模板精讲的内容基本上市面上的

[转帖]MySQL索引优化分析之性能分析(Explain执行计划)

一、MySQL常见瓶颈 二、性能分析工具Explain(执行计划 ) 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。查看官网说明: 使用: Explain + SQL语句 作用: 三、各字段解释 3.1、

使用shell脚本在Linux中管理Java应用程序

目录前言一、目录结构二、脚本实现1. 脚本内容2. 使用说明2.1 配置脚本2.2 脚本部署2.3 操作你的Java应用总结 前言 在日常开发和运维工作中,管理基于Java的应用程序是一项基础且频繁的任务。本文将通过一个示例脚本,展示如何利用Shell脚本简化这一流程,实现Java应用的一键式启动、

Netcode for Entities里如何对Ghost进行可见性筛选(1.2.3版本)

一行代码省流:SystemAPI.GetSingleton() 当你需要按照区域、距离或者场景对Ghost进行筛选的时候,Netcode for Entities里并没有类似FishNet那样方便的过滤方式,需要获取一个过滤专用的组件:GhostRelevancy。 这个结构的内容不多,但功能很强大

如何让Java编译器帮你写代码

本文结合京东监控埋点场景,对解决样板代码的技术选型方案进行分析,给出最终解决方案后,结合理论和实践进一步展开。通过关注文中的技术分析过程和技术场景,读者可收获一种样板代码思想过程和解决思路,并对Java编译器底层有初步了解。

[转帖]性能优化必备——火焰图

引言 本文主要介绍火焰图及使用技巧,学习如何使用火焰图快速定位软件的性能卡点。结合最佳实践实战案例,帮助读者加深刻的理解火焰图构造及原理,理解 CPU 耗时,定位性能瓶颈。 背景 当前现状 假设没有火焰图,你是怎么调优程序代码的呢?让我们来捋一下。 1. 功能开关法 想当年我刚工作,还是一个技术小白

【算法】数学之旅,根据素数特征寻找底数

当下午六点的钟声敲响,小悦如常地结束了一天的工作。她坐在工位上,脑海中不禁回想起自己学习数学的过程。那些数字、公式以及那些漫长夜晚的努力,都像是一段迷人的旋律,让她无法忘怀。当她沉浸在回忆中时,那迷人的微笑映入了旁人的眼帘,而这一幕恰好被一位同事捕捉到。 “你在笑什么呢?”同事好奇地问道。 “哦,没

Java7提供的Fork/Join框架实现高并发程序,你会使用吗?

摘要:Fork/Join框架位于J.U.C(java.util.concurrent)中,是Java7中提供的用于执行并行任务的框架,其可以将大任务分割成若干个小任务,最终汇总每个小任务的结果后得到最终结果。 本文分享自华为云社区《如何使用Java7提供的Fork/Join框架实现高并发程序?》,作