golang内存对齐详解

 更新时间:2023年10月16日 08:51:58   作者:写代码的lorre  
在golang中,每一种数据类型都有其对应的数据类型大小,也就是占用了多少内存空间,我们可以通过unsafe.Sizeof函数,来确定一个变量占用的内存字节数,本文将详细给大家介绍golang内存对齐,需要的朋友可以参考下

背景

在golang中,每一种数据类型都有其对应的数据类型大小,也就是占用了多少内存空间

我们可以通过unsafe.Sizeof函数,来确定一个变量占用的内存字节数

demo:

package main
import (
   "fmt"
   "testing"
   "unsafe"
)
func TestTypeSize(t *testing.T) {
   var a int8 = 4
   s1 := "hello world"
   s2 := "hahaha"
   fmt.Println(unsafe.Sizeof(a))  // 1字节
   fmt.Println(unsafe.Sizeof(s1)) // 16字节
   fmt.Println(unsafe.Sizeof(s2)) // 16字节
}

注意:

  • unsafe.Sizeof返回的是一个变量占用的内存字节数,而不是变量所表示内容占用的内存字节数。所以在上述demo中,尽管s1和s2的字符串内容不一样,但s1和s2的变量类型都是string,所以s1和s2占用的内存字节数相同

结构体大小

我们还可以通过unsafe.Sizeof来获取结构体占用的内存字节数

demo1:

package main
import (
   "fmt"
   "testing"
   "unsafe"
)
type demo1 struct {
   a int8
   b int16
}
func TestStructSize1(t *testing.T) {
   d1 := demo1{}
   fmt.Println(unsafe.Sizeof(d1.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d1.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d1))   // 4字节
}

问题1:

  • 结构体占用的内存字节数不等于结构体内各个字段占用的内存字节数之和。结构体内各个字段占用的内存字节数之和为:3字节 = 1字节(a占用字节数) + 2字节(b占用字节数),结构体占用字节数为4字节

demo2:

package main
import (
   "fmt"
   "testing"
   "unsafe"
)
type demo2 struct {
   a int8
   b int16
   c int32
   d int64
}
type demo3 struct {
   a int8
   d int64
   b int16
   c int32
}
func TestStructSize2(t *testing.T) {
   d2 := demo2{}
   fmt.Println(unsafe.Sizeof(d2.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d2.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d2.c)) // 4字节
   fmt.Println(unsafe.Sizeof(d2.d)) // 8字节
   fmt.Println(unsafe.Sizeof(d2))   // 16字节
   d3 := demo3{}
   fmt.Println(unsafe.Sizeof(d3.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d3.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d3.c)) // 4字节
   fmt.Println(unsafe.Sizeof(d3.d)) // 8字节
   fmt.Println(unsafe.Sizeof(d3))   // 24字节
}

问题2:

  • 当两个结构体内的字段类型一样时,字段顺序不同时,结构体占用的字节数也可能不同

出现上面两个问题的根本原因,就是本文要讨论的内容:内存对齐

什么是内存对齐

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐

简单来说,内存对齐就是把各种数据类型按照一定的规则,在内存空间进行排列,而不是直接按照顺序进行排列

为什么需要内存对齐

那么为什么需要进行内存对齐呢,主要原因有以下几点:

  • 性能原因:CPU为了加速对内存的访问速度,并不是一个字节一个字节的去访问内存,而是一次访问多个字节,一般称之为字长。32位CUP的字长一般是4字节,64位CPU的字长一般是8字节。如果没有进行内存对齐,那么CPU在访问一个变量时,可能需要进行多次读取,然后进行拼接,才能得到最终的变量内容。进行内存对齐后,可以减少CPU访问内存次数,提升性能

  • 更好的保证访问的原子性

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

如何进行内存对齐

常见数据类型的内存对齐

编译器按照每种数据类型的对齐边界来进行内存对齐

首先需要确认每种数据类型的对齐边界,对齐边界 = min(类型大小,平台字长)

常见数据类型在常见平台上的对齐边界:

类型类型大小64位平台字长64位平台对齐边界32位平台字长32位平台对齐边界
int81byte8byte1byte4byte1byte
int162byte8byte2byte4byte2byte
int324byte8byte4byte4byte4byte
int648byte8byte8byte4byte4byte
string16byte8byte8byte4byte4byte

为什么对齐边界需要取类型大小和平台字长的最小值呢?

答案是为了节省内存,避免内存浪费,提升读取的性能

  • 当类型大小 < 平台字长时,int8类型在64位平台进行内存对齐两种情况如图:

  • 当类型大小 > 平台字长时,int64类型在32位平台进行内存对齐两种情况如图:

结构体的内存对齐

结构体的对齐边界为:结构体内成员类型最大的对齐边界

结构体进行内存对齐的两个要求:

  • 起始地址是结构体对齐边界的倍数
  • 结构体整体占用字节数必须是结构体对齐边界的倍数。为了保证结构体数组的内存对齐

下面我们具体分析下结构体的内存对齐

type demo4 struct {
   a int8
   b int64
   c int32
   d int16
}

首先确定结构体demo4的对齐边界为:成员类型最大的对齐边界 = 8byte

结构体内存对齐如图:

结构体内存对齐的特殊情况

如果结构体的字段包含空结构体类型时

  • 空结构体类型字段不是最后一个字段时,不会占用内存
  • 空结构体类型字段是最后一个字段时,需要进行内存对齐,占用的内存大小和前一个字段的大小一致

demo:

package main
import (
   "fmt"
   "testing"
   "unsafe"
)
type demo5 struct {
   s struct{}
   a int8
}
type demo6 struct {
   a int8
   s struct{}
}
func TestStructSize3(t *testing.T) {
   d5 := demo5{}
   fmt.Println(unsafe.Sizeof(d5.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d5.s)) // 0字节
   fmt.Println(unsafe.Sizeof(d5))   // 1字节
   d6 := demo6{}
   fmt.Println(unsafe.Sizeof(d6.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d6.s)) // 0字节
   fmt.Println(unsafe.Sizeof(d6))   // 2字节
}

空结构体类型字段是最后一个字段时,需要占用内存,主要还是为了解决内存泄漏问题

内存泄漏问题分析:

以上就是golang内存对齐详解的详细内容,更多关于golang内存对齐的资料请关注脚本之家其它相关文章!

相关文章

  • 基于Go语言实现选择排序算法及优化

    基于Go语言实现选择排序算法及优化

    选择排序是一种简单的比较排序算法.这篇文章将利用Go语言实现冒泡排序算法,文中的示例代码讲解详细,对学习Go语言有一定的帮助,需要的可以参考一下
    2022-12-12
  • Golang递归获取目录下所有文件方法实例

    Golang递归获取目录下所有文件方法实例

    这篇文章主要给大家介绍了关于Golang递归获取目录下所有文件的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-02-02
  • Golang实现将视频按照时间维度剪切的工具

    Golang实现将视频按照时间维度剪切的工具

    这篇文章主要为大家详细介绍了如何利用Golang实现将视频按照时间维度进行剪切,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2022-12-12
  • Go Web 编程中的模板库应用指南(超详细)

    Go Web 编程中的模板库应用指南(超详细)

    这篇文章主要介绍了Go Web 编程中的模板库应用指南,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • 简单了解Go语言中函数作为值以及函数闭包的使用

    简单了解Go语言中函数作为值以及函数闭包的使用

    这篇文章主要介绍了简单了解Go语言中函数作为值以及函数闭包的使用,是golang入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • 一文带你熟悉Go语言中的分支结构

    一文带你熟悉Go语言中的分支结构

    这篇文章主要和大家分享一下Go语言中的分支结构(if - else-if - else、switch),文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的可以参考一下
    2022-11-11
  • Golang之sync.Pool对象池对象重用机制总结

    Golang之sync.Pool对象池对象重用机制总结

    这篇文章主要对Golang的sync.Pool对象池对象重用机制做了一个总结,文中有相关的代码示例和图解,具有一定的参考价值,需要的朋友可以参考下
    2023-07-07
  • go按行读取文件的三种实现方式汇总

    go按行读取文件的三种实现方式汇总

    最近有遇到需要用go读取文件的情况,下面这篇文章主要给大家介绍了关于go按行读取文件的三种实现方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • golang使用sync.singleflight解决热点缓存穿透问题

    golang使用sync.singleflight解决热点缓存穿透问题

    在go的sync包中,有一个singleflight包,里面有一个 singleflight.go文件,代码加注释,一共200行出头,通过 singleflight可以很容易实现缓存和去重的效果,避免重复计算,接下来我们就给大家详细介绍一下sync.singleflight如何解决热点缓存穿透问题
    2023-07-07
  • 详解Go语言如何实现并发安全的map

    详解Go语言如何实现并发安全的map

    go语言提供的数据类型中,只有channel是并发安全的,基础map并不是并发安全的,本文为大家整理了三种实现了并发安全的map的方案,有需要的可以参考下
    2023-12-12

最新评论