内存对齐

什么是内存对齐

弄明白什么是内存对齐的时候,先来看一个demo

type s struct {
Bool bool
Byte byte
In32 int32
Int64 int64
Int8 int8
} 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("E")) part1 := s{}
fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
}

打印下输出

bool size: 1
int32 size: 4
int8 size: 1
int64 size: 8
byte size: 1
string size: 16 part1 size: 24, align: 8

按照不能类型的长度计算,结构体s的长度应该是1+1+4+8+1=15,但是我们通过unsafe.Sizeof()计算出来的长度是24。这就是发生了内存对齐造成的。

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

为什么需要内存对齐

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

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

减少次数

我们认为的内存空间可能是简单的字节数组

但是处理器对待内存不是这样的,CPU把内存当作一块一块的,块的大小可以是2、4、8、16字节大小,因此CPU读取内存是一块一块读取的。(块的大小称为内存读取粒度)

如果没有内存对齐:

假设当前内存读取粒度为4

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作。

如果内存对齐了:

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

保障原子性

如果地址未对齐,则至少需要两次内存访问。但是,如果所需数据跨越虚拟内存的两页会发生什么?这可能导致驻留第一页而没有驻留最后一页的情况。访问时,在指令中间,将产生页面错误,执行虚拟内存管理交换代码,从而破坏指令的原子性。

合理的内存对齐可以提高内存读写的性能,并且便于实现变量操作的原子性。

对齐系数

在不同平台上的编译器都有自己默认的 “对齐系数”,可通过预编译命令#pragma pack(n)进行变更,n就是代指 “对齐系数”。

一般来讲,我们常用的平台的系数如下:

  • 32 位:4

  • 64 位:8

查看几种类型的对齐系数

func main() {
fmt.Printf("bool align: %d\n", unsafe.Alignof(bool(true)))
fmt.Printf("int32 align: %d\n", unsafe.Alignof(int32(0)))
fmt.Printf("int8 align: %d\n", unsafe.Alignof(int8(0)))
fmt.Printf("int64 align: %d\n", unsafe.Alignof(int64(0)))
fmt.Printf("byte align: %d\n", unsafe.Alignof(byte(0)))
fmt.Printf("string align: %d\n", unsafe.Alignof("test"))
fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
}

对齐系数

bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
string align: 8
map align: 8

不同的平台对齐系数可能不同,根据电脑的实际情况做分析

对齐规则

1、对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的起始位置必须是默认对齐长度和该数据成员长度中最小的长度的倍数。

2、除了结构成员需要对齐,结构本身也需要对齐,结构的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。

我们根据上面的例子具体的分析下

type s struct {
Bool bool
Byte byte
In32 int32
Int64 int64
Int8 int8
}

对齐流程

  • 第一个成员Bool

    • 类型: bool
    • 大小/对齐值: 1
    • 占用地址空间: 1
    • 结构内存起始偏移量: 0
    • 对齐Padding空间:0
    • 对齐当前偏移量: 1
  • 第二个成员Byte
    • 类型: byte
    • 大小/对齐值: 1
    • 占用地址空间: 1
    • 结构内存起始偏移量: 1
    • 对齐Padding空间:0 根据规则1,当前成员大小/对齐值1,所以不用padding
    • 对齐当前偏移量: 2
  • 第三个成员In32
    • 类型: int32
    • 大小/对齐值: 4
    • 占用地址空间: 4
    • 结构内存起始偏移量: 2
    • 对齐Padding空间:2 根据规则1,当前成员大小/对齐值4,但是结构内存起始偏移量是2,2不是4的最小倍数,所以要padding空间为2
    • 对齐当前偏移量: 8
  • 第四个成员Int64
    • 类型: int64
    • 大小/对齐值: 8
    • 占用地址空间: 8
    • 结构内存起始偏移量: 8
    • 对齐Padding空间:0 根据规则1,当前成员大小/对齐值8,当前结构内存起始偏移量是8,所以不用padding
    • 对齐当前偏移量: 16
  • 第五个成员Int8
    • 类型: nt8
    • 大小/对齐值: 4
    • 占用地址空间: 4
    • 结构内存起始偏移量: 16
    • 对齐Padding空间:0 根据规则1,当前成员大小/对齐值4,当前结构内存起始偏移量是16,所以不用padding
    • 对齐当前偏移量: 20
  • 结束
    • 起始地址: 20
    • 对齐Padding空间:4 根据规则2,当前成员中最长类型中最小的数据大小为8,当前结构内存起始偏移量是20,所以要padding空间为4
    • 对齐当前偏移量: 24

内存对齐主要的依据就是上面两个对齐的规则,数据成员的起始位置必须是默认对齐长度和该数据成员长度中最小的长度的倍数,同时对于整个结构对象,最终的地址要是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数。

总结

1、内存对齐是不可缺少的,能够减少cpu的访问内存的次数;

2、合理的内存布局也便于实现变量操作的原子性;

3、不同硬件平台占用的大小和对齐值都可能是不一样的。

参考

【在 Go 中恰到好处的内存对齐】https://eddycjy.gitbook.io/golang/di-1-ke-za-tan/go-memory-align

【golang 内存对齐】https://xie.infoq.cn/article/594a7f54c639accb53796cfc7

【C/C++内存对齐详解】https://zhuanlan.zhihu.com/p/30007037

【内存对齐详解】https://developer.aliyun.com/article/32177

【go内存对齐】https://www.kancloud.cn/golang_programe/golang/1144263

【Go struct 内存对齐】https://geektutu.com/post/hpg-struct-alignment.html

【Memory access granularity】https://developer.ibm.com/articles/pa-dalign/?mhsrc=ibmsearch_a&mhq=Straighten up and fly right

【为什么要内存对齐 Data alignment: Straighten up and fly right】https://blog.csdn.net/lgouc/article/details/8235471

什么是内存对齐,go中内存对齐分析的更多相关文章

  1. c++内存中字节对齐问题详解

    一.什么是字节对齐,为什么要对齐?    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...

  2. C语言中内存对齐方式

    一.什么是对齐,以及为什么要对齐: 1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问, ...

  3. C语言中内存对齐规则讨论(struct)

    C语言中内存对齐规则讨论(struct) 对齐: 现代计算机中内存空间都是按着byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地 ...

  4. C/C++中内存对齐问题的一些理解(转)

    内存对齐指令 一般来说,内存对齐过程对coding者来说是透明的,是由编译器控制完成的 如对内存对齐有明确要求,可用#pragma pack(n)指定,以n和结构体中最长数据成员长度中较小者为有效值 ...

  5. C语言中内存对齐与结构体

    结构体 结构体是一种新的数据类型,对C语言的数据类型进行了极大的扩充. struct STU{ int age; char name[15]; }; struct STU a; //结构体实例 str ...

  6. 重磅硬核 | 一文聊透对象在 JVM 中的内存布局,以及内存对齐和压缩指针的原理及应用

    欢迎关注公众号:bin的技术小屋 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以 ...

  7. c/c++中内存对齐完全理解

    一,什么是内存对齐?内存对齐用来做什么? 所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段. 比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存 ...

  8. 关于arm处理器 内存编址模式 与 字节对齐方式 (转)

    转自:http://bavon.bokee.com/5429805.html 在x86+Linux上写的程序,在PC机上运行得很好.可是使用ARM的gcc进行交叉编译,再送到DaVinci目标板上运行 ...

  9. GNU C - 关于8086的内存访问机制以及内存对齐(memory alignment)

    一.为什么需要内存对齐? 无论做什么事情,我都习惯性的问自己:为什么我要去做这件事情? 是啊,这可能也是个大家都会去想的问题, 因为我们都不能稀里糊涂的或者.那为什么需要内存对齐呢?这要从cpu的内存 ...

随机推荐

  1. poj1787 Charlie's Change

    Description Charlie is a driver of Advanced Cargo Movement, Ltd. Charlie drives a lot and so he ofte ...

  2. 考研路茫茫——单词情结 HDU - 2243 AC自动机 && 矩阵快速幂

    背单词,始终是复习英语的重要环节.在荒废了3年大学生涯后,Lele也终于要开始背单词了. 一天,Lele在某本单词书上看到了一个根据词根来背单词的方法.比如"ab",放在单词前一般 ...

  3. 一道思维题 &&递归改循环

    思路: 比如5 2 12345--> 1245 从3开始,这时候5变成了1.剩下4512,对应1234.只需要找到现在n-1,k中的数对应原来的编号的映射. 比如1-->3 是1+2 mo ...

  4. codeforces 903D

    D. Almost Difference time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  5. POJ3233 构造子矩阵+矩阵快速幂

    题意:给你矩阵A,求S=A+A^1+A^2+...+A^n sol:直接把每一项解出来显然是不行的,也没必要. 我们可以YY一个矩阵: 其中1表示单位矩阵 然后容易得到: 可以看出这个分块矩阵的左下角 ...

  6. codevs1039整数的k划分-思考如何去重复

    题目描述将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序).例如:n=7,k=3,下面三种划分方案被认为是相同的.1 1 51 5 15 1 1问有多少种不同的分法.输入描述输入 ...

  7. Commons Collections2分析

    0x01.POC分析 //创建一个CtClass对象的容器 ClassPool classPool=ClassPool.getDefault(); //添加AbstractTranslet的搜索路径 ...

  8. Cortex-M系列内核 启动文件分析

    最近终于闲了下来了准备好好学习下Cortex-M3/M4系列处理器的架构,经过各种资料的折磨也没法对它的整个工作过程能有个完整的认知,最后看到一片博客打算从程序的运行过程开始探究,所以首先就找到了启动 ...

  9. matplotlib 图标显示中文

    matplotlib 显示中文 Method_1: # 添上: plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcPara ...

  10. JavaScript var, let, const difference All In One

    JavaScript var, let, const difference All In One js var, let, const 区别 All In One 是否存在 hoisting var ...