当前位置:首页 > Golang > 正文内容

关于Go的内存对齐

周伯通2020-11-08Golang224

今天看到群里有人提到内存对齐的东西,网上查阅了golang相关的内存对齐资料,特意整理了下,希望对大家有帮助。
看完这篇介绍。

我们将获得以下知识点:
1.什么是内存对齐?
2.为什么需要内存对齐?
3.如何进行内存对齐?
4.golang的内存对齐如何体现?
5.如何利用内存对齐来优化golang?
我们来先看一个结构体:

package main

import (
   "fmt"
   "unsafe"
)

type Test1 struct {
   a bool
   b int32
   c int8
   d int64
   e byte
}
func main() {
   fmt.Printf("bool size: %d\n", unsafe.Sizeof(bool(true)))
   fmt.Printf("int32 size: %d\n", unsafe.Sizeof(int32(0)))
   fmt.Printf("int8 size: %d\n", unsafe.Sizeof(int8(0)))
   fmt.Printf("int64 size: %d\n", unsafe.Sizeof(int64(0)))
   fmt.Printf("byte size: %d\n", unsafe.Sizeof(byte(0)))
   fmt.Printf("string size: %d\n", unsafe.Sizeof("测试字符串值"))
}

输出:

bool size: 1
int32 size: 4
int8 size: 1
int64 size: 8
byte size: 1
string size: 16

这么一算,Test1 这一个结构体的占用内存大小为 1+4+1+8+1 = 15 个字节。相信有部分博友是这么算的,貌似看上去也没什么毛病,
真实情况是怎么样的呢?我们实际调用看看,如下:

package main

import (
   "fmt"
   "unsafe"
)

type Test1 struct {
   a bool
   b int32
   c int8
   d int64
   e byte
}

func main() {
   test1 := Test1{}
   fmt.Printf("结构体 长度: %d, 对齐: %d\n", unsafe.Sizeof(test1), unsafe.Alignof(test1))
}

输出结果:

结构体 长度: 32, 对齐: 8

最终输出为占用 32 个字节。这与前面所预期的结果完全不一样。
    从结果可以看到bool int8 int32 int64 byte的大小,但是相加的结果并不是Test1结构体的结果,从结果可以看出来,bool占用了一个字节后,下一个int32类型占用4字节时是有4个字节偏移的,所以一共占用了八个字节,向后推导发现有些类型占用了比类型本身字节更大的空间。
导致这个问题的原因就是内存对齐。这充分地说明了先前的计算方式是错误的。为什么呢?

在这里要提到 “内存对齐” 这一概念,才能够用正确的理解去计算,接下来我们详细的讲讲它是什么 

什么是内存对齐

有的博友们可能会认为内存读取,就是一个简单的字节数组摆放

1.jpg
上图表示一个坑一个萝卜的内存读取方式。但实际上 CPU 并不会以一个一个字节去读取和写入内存。相反 CPU 读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小。块大小我们称其为内存访问粒度。如下图:
2.jpg
在样例中,假设访问粒度为 4。 CPU 是以每 4 个字节大小的访问粒度去读取和写入内存的。为了进一步加深对内存对齐的正确理解。我们这样:
在想象中内存应该是一个一个独立的字节组成的。像这样:
3.jpg
事实上,别人是这样的:
4.jpg
内存是按照成员的声明顺序,依次分配内存,第一个成员偏移量是0,其余每个成员的偏移量为指定数的整数倍数。像这样进行内存的分配叫做内存对齐。

为什么要关心对齐

  • 你正在编写的代码在性能(CPU、Memory)方面有一定的要求

  • 你正在处理向量方面的指令

  • 某些硬件平台(ARM)体系不支持未对齐的内存访问

另外作为一个工程师,你也很有必要学习这块知识点哦 :)

为什么要做对齐

  • 平台(移植性)原因:
        不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况.并不是所有的硬件平台都能访问任意地址上的任意数据,会直接报错的!(解释:比如说有的cpu读取4个字节数据,要是没有内存对齐,从1开始那么内存就需要把0-7字节的全部取出来,再剔除掉1/5/6/7,增加了额外的操作,cpu不一定能这么搞,自然就报错了)。


  • 性能原因:
        若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作.访问未对齐的内存,需要访问两次;如果对齐的话就只需要一次了。(解释:比如取int64,按照8个位对齐好了,那获取的话直接就是获取8个字节就好了,边界好判断)。

  • 内存对齐原则

  • 对齐值为系统默认对齐值和类型大小长度的最小值

  • 结构体内部字段对齐值为默认对齐值和字段最大类型长度的最小值。

  • 再说简单点就是二个原则:

  • 1.具体类型,对齐值=min(编译器默认对齐值,类型大小Sizeof长度)

  • 2.struct每个字段内部对齐,对齐值=min(默认对齐值,字段最大类型长度)

内存对齐的使用

由于内存对齐的存在,使得类型按照不同的顺序排列属性可能会得到不同的大小,所以在受限条件下设计类型时需要注意。结构体是平时写代码经常用到的。相同的成员,不同的排列顺序,会有什么区别?

我们举个例子:

package main

import (
   "fmt"
   "unsafe"
)

type Test1 struct {
   a bool
   b int32
   c int8
   d int64
   e byte
}
type Test2 struct {
   a bool
   e byte
   c int8
   b int32
   d int64
}

func main() {
   t1 := Test1{}
   t2 := Test2{}
   fmt.Printf("test1 大小: %d\n", unsafe.Sizeof(t1))
   fmt.Printf("test2 大小: %d\n", unsafe.Sizeof(t2))
}

输出:

test1 大小: 32
test2 大小: 16

从结果可以看到,顺序不用对类型体积还是有影响的。下面是对类型内存占用的示意图,+表示占空。

t1 : |a+++|bbbb|c+++|++++|dddd|dddd|e+++|++++|

t2 : |aec+|bbbb|dddd|dddd|

从上面的示意可以看出test1和test2的占用方式,t2相比t1减少了一半属性占用空间。


    扫描二维码推送至手机访问。

    版权声明:本文由周伯通的博客发布,如需转载请注明出处。

    本文链接:http://zhoubotong.site/post/10.html

    分享给朋友:

    相关文章

    关于go协程同步的几种方法

    这里咋们简要介绍下关于go中协程的几种同步方法。先说下概念性的:协程概念简要理解协程有点类似线程,是一种更为轻量级的调度单位,但协程还是不同于线程的,线程是系统级实现的,常见的调度方法是时间片轮转法,...

    go协程全局变量和局部变量

    大家可能经常会用到类似如下代码片段:package main import (    "fmt"   ...

    Go 数组合并去重和排序

            Sort包实现了四种基本排序算法:插入排序、归并排序、堆排序和快速排序。 但是这四种排序方法是不公开的,...

    Go slice初始化转换json

            切片slice的声明和初始化 , 如果我们只是单纯的声明 var list []string ...

    Go map定义的几种方式以及修改技巧

    直入正题,我们看下以下代码:package main import (    "encoding/json"  ...

    发表评论

    访客

    看不清,换一张

    ◎欢迎参与讨论,请在这里发表您的看法和观点。