弄明白什么是内存对齐的时候,先来看一个demohtml
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)) }
打印下输出git
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。这就是发生了内存对齐形成的。golang
现代计算机中内存空间都是按照byte
划分的,从理论上讲彷佛对任何类型的变量的访问能够从任何地址开始,但实际状况是在访问特定类型变量的时候常常在特定的内存地址访问, 这就须要各类类型数据按照必定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是内存对齐。数组
一、平台缘由(移植缘由):不是全部的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,不然抛出硬件异常。数据结构
二、性能缘由:数据结构(尤为是栈)应该尽量地在天然边界上对齐。缘由在于,为了访问未对齐的内存,处理器须要做两次内存访问;而对齐的内存访问仅须要一次访问。布局
咱们认为的内存空间多是简单的字节数组post
可是处理器对待内存不是这样的,CPU把内存看成一块一块的,块的大小能够是二、四、八、16字节大小,所以CPU读取内存是一块一块读取的。(块的大小称为内存读取粒度)性能
若是没有内存对齐:.net
假设当前内存读取粒度为43d
假如没有内存对齐机制,数据能够任意存放,如今一个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
不一样的平台对齐系数可能不一样,根据电脑的实际状况作分析
一、对于结构的各个成员,第一个成员位于偏移为0的位置,之后每一个数据成员的起始位置必须是默认对齐长度和该数据成员长度中最小的长度的倍数。
二、除告终构成员须要对齐,结构自己也须要对齐,结构的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。
咱们根据上面的例子具体的分析下
type s struct { Bool bool Byte byte In32 int32 Int64 int64 Int8 int8 }
对齐流程
Bool
Byte
大小/对齐值
1,因此不用paddingIn32
大小/对齐值
4,可是结构内存起始偏移量是2,2不是4的最小倍数,因此要padding空间为2Int64
大小/对齐值
8,当前结构内存起始偏移量是8,因此不用paddingInt8
大小/对齐值
4,当前结构内存起始偏移量是16,因此不用padding内存对齐主要的依据就是上面两个对齐的规则,数据成员的起始位置必须是默认对齐长度和该数据成员长度中最小的长度的倍数,同时对于整个结构对象,最终的地址要是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数。
一、内存对齐是不可缺乏的,可以减小cpu的访问内存的次数;
二、合理的内存布局也便于实现变量操做的原子性;
三、不一样硬件平台占用的大小和对齐值均可能是不同的。
【在 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