Go内存分配之结构体优化技巧

 更新时间:2023年11月22日 11:02:04   作者:洛天枫  
这篇文章主要为大家详细介绍了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 int8
    b string
    c int8
    d 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 int8
    c int8
    b string
    d 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 string
    d int32
    a int8
    c 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(填充)。

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

到此这篇关于Go内存分配之结构体优化技巧的文章就介绍到这了,更多相关Go结构体内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Go语言中获取文件路径的不同方法与应用场景

    详解Go语言中获取文件路径的不同方法与应用场景

    在使用 Go 开发项目时,估计有不少人遇到过无法正确处理文件路径的问题,本文将尝试从简单到复杂,详细介绍 Go 中获取路径的不同方法及应用场景,希望对大家有所帮助
    2024-02-02
  • 让GPT教你用go语言和C语言开发IDE配置学习

    让GPT教你用go语言和C语言开发IDE配置学习

    这篇文章主要介绍了让GPT教你用go语言和C语言开发IDE配置学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 解决GO编译时避免引入外部动态库的问题

    解决GO编译时避免引入外部动态库的问题

    最近碰到一个问题,有一个流量采集的组件中使用到了github.com/google/gopacket 这个库,这个库使用一切正常,但是唯独有一个缺点,编译后的二进制文件依赖于libpcap.so的动态库,这篇文章主要介绍了GO编译时避免引入外部动态库的解决方法,需要的朋友可以参考下
    2022-10-10
  • 在Go语言中使用JSON的方法

    在Go语言中使用JSON的方法

    这篇文章主要介绍了在Go语言中使用JSON的相关资料,需要的朋友可以参考下
    2018-02-02
  • Go channel实现批量读取数据

    Go channel实现批量读取数据

    Go中的 channel 其实并没有提供批量读取数据的方法,需要我们自己实现一个,使用本文就来为大家大家介绍一下如何通过Go channel实现批量读取数据吧
    2023-12-12
  • 基于Go语言编写一个能在Windows上录音的小程序

    基于Go语言编写一个能在Windows上录音的小程序

    这篇文章主要为大家详细介绍了如何基于Go语言编写一个能在Windows上录音的小程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2026-03-03
  • Golang监听日志文件并发送到kafka中

    Golang监听日志文件并发送到kafka中

    这篇文章主要介绍了Golang监听日志文件并发送到kafka中,日志收集项目的准备中,本文主要讲的是利用golang的tail库,监听日志文件的变动,将日志信息发送到kafka中 ,需要的朋友可以参考一下
    2022-04-04
  • go语言接口之接口值举例详解

    go语言接口之接口值举例详解

    接口是一种抽象类型,是对其他类型行为的概括与抽象,从语法角度来看,接口是一组方法定义的集合,下面这篇文章主要给大家介绍了关于go语言接口之接口值的相关资料,文章通过代码介绍的非常详细,需要的朋友可以参考下
    2024-06-06
  • 一文带你掌握掌握 Golang结构体与方法

    一文带你掌握掌握 Golang结构体与方法

    在 Golang 中,结构体和方法是实现面向对象编程的重要组成部分,也是 Golang 的核心概念之一。在本篇文章中,我们将深入介绍 Golang 结构体与方法的概念、使用方法以及相关的编程技巧和最佳实践
    2023-04-04
  • Go高级特性探究之处理1分钟百万请求详解

    Go高级特性探究之处理1分钟百万请求详解

    对于大型的互联网应用程序,如电商平台、社交网络、金融交易平台等,每秒钟都会收到大量的请求,那么Go是如何处理这些百万请求的呢,下面就来和大家详细讲讲
    2023-06-06

最新评论