Go 内存分配:结构体中的优化技巧

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

在使用Golang进行内存分配时,我们需要遵循一系列规则。在深入了解这些规则之前,我们需要先了解变量的对齐方式。

Golang的unsafe包中有一个函数Alignof,签名如下:

func Alignof(x ArbitraryType) uintptr

对于任何类型为v的变量xAlignOf函数会返回该变量的对齐方式。我们将对齐方式记为m。现在,Golang确保m是满足变量x的内存地址 % m == 0的最大可能数,也就是说,变量x的内存地址是m的倍数。

让我们来看看一些数据类型的对齐方式:

  • byteint8uint8 -> 1
  • int16uint16 -> 2
  • int32uint32float32complex64 -> 4
  • intint64uint64float64complex128 -> 8
  • stringslice -> 8

对于结构体中的字段,行为可能会有所不同,详细信息请参考包的文档。

为了更好地理解结构体内存分配的情况,我们将使用unsafe包中的另一个函数Offsetof。该函数返回字段相对于结构体起始位置的位置,换句话说,它返回字段起始位置与结构体起始位置之间的字节数。

func Offsetof(x ArbitraryType) uintptr

为了更好地理解结构体内存分配,让我们以一个示例结构体为例:

type Example struct {a int8b stringc int8d int32
}

现在,我们将找出类型为Example的变量所占用的总内存,并尝试优化分配。

var v = Example{a: 10,b: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus rhoncus.",c: 20,d: 100,
}
fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 输出:0
fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 输出:8
fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 输出:24
fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 输出:28

现在,问题出现了:“为什么结构体中字段b的偏移量是8?它应该是1,因为字段a的类型是int8,只占用1个字节。”回到字符串数据类型的对齐方式,它的值为8,这意味着地址需要被8整除,因此在其中插入了7个字节的“填充”,以确保这种行为。

为什么字段c的偏移量是24?字段b中的字符串看起来比16个字节要长得多,如果字符串的偏移量是8,那么字段c的偏移量应该更大一些。

上述问题的答案是,在Go中,字符串并不是在结构体内的同一位置分配内存的。有一个单独的数据结构来保存字符串描述符,并且该字符串描述符以原地方式存储在结构体中,用于类型为string的字段,该描述符的大小为16个字节。

现在,让我们来看看unsafe包中的另一个函数Sizeof。正如其名称所示,该函数估计并返回类型为x的变量所占用的字节数。

注意:它是根据结构体中可能存在的不同大小的字段来估计大小的。

func Sizeof(x ArbitraryType) uintptr

现在,让我们来看看我们的结构体Example的大小。

fmt.Println("Example的大小:", unsafe.Sizeof(v)) // 输出:32

我们如何优化这个结构体以最小化填充呢?

为了优化这个结构体的内存,我们将查看不同数据类型的对齐方式,并尝试减少填充。让我们尝试将两个int8类型的字段放在一起。

type y struct {a int8c int8b stringd int32
}var v = y{}
fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 输出:0
fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 输出:8
fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 输出:1
fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 输出:24
fmt.Println("Example的大小:", unsafe.Sizeof(v)) // 输出:32

太棒了,我们去掉了一些填充,但是为什么大小仍然是32?大小应该是1(a)+ 1(c)+ 6(填充)+ 16(b)+ 4(d)= 28

现在,当结构体的最后一个字段与架构的对齐要求不完全一致时,会在最后一个字段之后添加填充,以确保结构体的整体大小是其字段中最大对齐要求的倍数。因为字符串数据类型的最大对齐方式为8,所以额外添加了填充,使大小成为8的倍数,即在末尾填充了4个字节,使大小为32字节。

我们能否进一步减少填充,使其更加优化?

让我们尝试通过移动字段位置来实现。

type y struct {b stringd int32a int8c int8
}var v = y{}
fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 输出:20
fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 输出:0
fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 输出:21
fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 输出:16
fmt.Println("Example的大小:", unsafe.Sizeof(v)) // 输出:24

我们可以看到,通过重新排列字段的位置,使得对齐需要最小化填充,我们已经将结构体的大小从32减小到24,这是内存优化的巨大进步,达到了25%。

当前的内存占用是16(b)+ 4(d)+ 1(a)+ 1(b)+ 2(填充)。

遗憾的是,由于语言和架构的限制,我们无法进一步去除填充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152722.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过微软MediaCreationTool制作Win10系统安装U盘,安装纯净版Win10的通用教程

最近新入手了一台Lenovo的入门级主机。 为了避免以后忘记装机步骤,特写下此博客记录。 装机步骤是在Lenovo网站上看的,在这表示感谢。 https://iknow.lenovo.com.cn/detail/177365

Ubuntu下发送邮件

mail命令在Ubuntu下是需要安装的,使用下条命令进行安装: sudo apt-get install heirloom-mailx 接下来输入用户密码,等待安装完成 此时还不能发送外部服务器邮件,需要完成以下配置,修改/etc/nail.rc或者/etc/s-nail.…

(十二)Flask重点之session

session 自我介绍&基本使用: 在Flask中,Session是一种用于在客户端和服务器之间存储和传输数据的机制。它允许您在用户与应用程序之间保持状态,并且可以存储和检索有关特定用户的信息。 Flask使用Werkzeug库提供的SecureCookie来实现S…

LangChain 4用向量数据库Faiss存储,读取YouTube的视频文本搜索Indexes for information retrieve

接着前面的Langchain,继续实现读取YouTube的视频脚本来问答Indexes for information retrieve LangChain 实现给动物取名字,LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗…

Python接口自动化(什么是接口、接口优势、类型)

简介 经常听别人说接口测试,接口测试自动化,但是你对接口,有多少了解和认识,知道什么是接口吗?它是用来做什么的,测试时候要注意什么?坦白的说,笔者之前也不是很清楚。接下来先看一下…

Python懒羊羊

目录 系列文章 写在前面 绘图基础 懒羊羊 写在后面 系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595…

requests库出现AttributeError问题的修复与替代方法

在使用App Engine时,开发者们通常会面临需要发送爬虫ip请求的情况,而Python中的requests库是一个常用的工具,用于处理爬虫ip请求。然而,在某些情况下,开发者可能会遇到一个名为AttributeError的问题,特别是…

力扣贪心——跳跃游戏I和II

1 跳跃游戏 利用边界进行判断,核心就是判定边界,边界内所有步数一定是最小的,然后在这个边界里找能到达的最远地方。 1.1 跳跃游戏I class Solution {public boolean canJump(int[] nums) {int len nums.length;int maxDistance 0;int te…

Day36力扣打卡

打卡记录 T 秒后青蛙的位置(DFS) 链接 class Solution:def frogPosition(self, n: int, edges: List[List[int]], t: int, target: int) -> float:g [[] for _ in range(n 1)]for x, y in edges:g[x].append(y)g[y].append(x)g[1].append(0)ans …

力扣labuladong——一刷day44

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣298. 二叉树最长连续序列二、力扣988. 从叶结点开始的最小字符串三、力扣1022. 从根到叶的二进制数之和四、力扣1457. 二叉树中的伪回文路径五、力扣 前…

Python编程技巧 – 使用字典

Python编程技巧 – 使用字典 Python Programming Skills – Using Dictionary Dictionary, 即字典,这是Python语言的一种重要的数据结构;Python字典是以键(key)值(value)对为元素,来存储数据的集合。 前文提到Python列…

Linux--网络编程

一、网络编程概述1.进程间通信: 1)进程间通信的方式有**:管道,消息队列,共享内存,信号,信号量这么集中 2)特点:依赖于linux内核,基本是通过内核来实现应用层…

Android Binder 跨进程通信的优势是什么

Android Binder 跨进程通信的优势是什么 Android Binder 是 Android 系统中用于实现跨进程通信的底层机制,具有以下优势: 高效性:Android Binder 使用共享内存技术,在进程间传递数据时不需要进行数据拷贝,从而提高了传…

【亚马逊云科技产品测评】活动征文|aws云服务器 + 微服务Spring Cloud Nacos 实战

文章目录 前言一、拥有一台Aws Linux服务器1.1、选择Ubuntu版本Linux系统1.2、创建新密钥对1.3、网络设置1.4、配置成功,启动实例1.5、回到实例区域1.6、进入具体的实例1.7、设置安全组 二、在Mac上连接Aws云服务,并安装配置JDK112.1、解决离奇的错误2.2…

RPC vs. HTTP:谁主沉浮在网络通信的江湖?

🎏:你只管努力,剩下的交给时间 🏠 :小破站 RPC vs. HTTP:谁主沉浮在网络通信的江湖? 前言第一:背景介绍第二:性能对比第三:适用场景第四:总结 前言…

golang学习笔记——要求用户输入一个数字,如果该数字为负数,则进入紧急状态

要求用户输入一个数字,如果该数字为负数,则进入紧急状态 编写一个要求用户输入一个数字的程序。 在开始时使用以下代码片段: package mainimport "fmt"func main() {val : 0fmt.Print("Enter number: ")fmt.Scanf(&quo…

代码随想录 Day50 单调栈 LeetCodeT503 下一个最大元素II T42接雨水

前言 前面我们说到了单调栈的第一题,下一个最大元素I,其实今天的两道题都是对他的变种,知道第一个单调栈的思想能够想清楚,其实这道题是很简单的 考虑好三个状态,大于等于小于,其实对于前面这些题目只要细心的小伙伴就会发现其实小于和等于的处理是一样的都是直接入栈,只有大于…

【Ubuntu】Windows远程Ubuntu系统

步骤 开启ssh服务并开放22端口关闭防火墙ufw或iptables ;或者将远程端口添加到入站与出站规则安装xrdp并将xrdp用户添加到ssl-cert用户组mstsc 远程,输入账号密码 1、开启ssh服务 1.1. 查看ssh是否已经开启 sudo ps -e | grep ssh如果最后返回是sshd…

Go并发编程学习-class1

class1. Mutex 解决资源并发访问 基础概念 临界区概念:一个被共享的资源,可以被并发访问。通过Mutex互斥锁,可以限定临界区只能由一个线程获取。 根据不同情况,不同适用场景 ●共享资源。并发地读写共享资源,会出现…

shell 脚本变量

目录 什么是 shell shell 的两种面向对象 shell 脚本概述 脚本 (本质为程序) 脚本的组成 执行脚本 例题 脚本构成 脚本执行逻辑及执行方式 脚本的常见错误 编写 shell 脚本 执行脚本文件的方式 重定向 重定向操作 shell 变量的作用及类型…