Golang优化之内存对齐

Posted 咖啡色的羊驼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang优化之内存对齐相关的知识,希望对你有一定的参考价值。

前文

话说今天在用uintptr进行指针运算的时候,突然想起来有个内存对齐的东西,那么对这个uintptr计算是否会有影响?

带着疑问,开始吧。

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

正文

1.什么是内存对齐?

在想象中内存应该是一个一个独立的字节组成的。像这样:

事实上,人家是这样的:

内存是按照成员的声明顺序,依次分配内存,第一个成员偏移量是0,其余每个成员的偏移量为指定数的整数倍数(图中是4)。像这样进行内存的分配叫做内存对齐。

2.为什么需要内存对齐?

原因有两点:

平台原因

并不是所有的硬件平台都能访问任意地址上的任意数据,会直接报错的!
(解释:比如说有的cpu读取4个字节数据,要是没有内存对齐,从1开始那么内存就需要把0-7字节的全部取出来,再剔除掉1/5/6/7,增加了额外的操作,cpu不一定能这么搞,自然就报错了)

性能原因

访问未对齐的内存,需要访问两次;如果对齐的话就只需要一次了。
(解释:比如取int64,按照8个位对齐好了,那获取的话直接就是获取8个字节就好了,边界好判断)

3.如何进行内存对齐?

二个原则:
1.具体类型,对齐值=min(编译器默认对齐值,类型大小Sizeof长度)
2.struct每个字段内部对齐,对齐值=min(默认对齐值,字段最大类型长度)

4.golang的内存对齐如何体现?

4.1 结构体的相同成员不同顺序

结构体是平时写代码经常用到的。相同的成员,不同的排列顺序,会有什么区别吗?

举个例子:

func main() 
	fmt.Println(unsafe.Sizeof(struct 
		i8  int8
		i16 int16
		i32 int32
	))
	fmt.Println(unsafe.Sizeof(struct 
		i8  int8
		i32 int32
		i16 int16
	))

输出:

8
12

what?竟然不一样。
分析一波:需要内存对齐的话,因为最大是int32,所以最终记过必须是4个字节的倍数才能对齐。
当8-16-32的时候,类似这样|x-xx|xxxx|。
当8-32-16的时候,类似这样|x—|xxxx|xx–|。
一眼就看出了大小了。

这里的为什么是x-xx而不是xxx-需要说明下。因为当int8放入内存的时候,其占坑1个字节,对齐值为1,而int16占坑2个字节,对齐值为2,所以说会先偏移2个字节从第三个字节才开始放int16的数

4.2指针运算

现在对结构体Test通过指针计算的方式进行赋值。
Test内存情况:|x-xx|xxxx|。需要注意的是这里的“-”需要多计算一个字节才行。

type Test struct 
	i8  int8
	i16 int16
	i32 int32



func main() 
	var t = new(Test)
	// 从0开始
	var i8 = (*int8)(unsafe.Pointer(t))
	*i8 = int8(10)
	
	// 偏移int8+1的字节数,注意这里有个1!!! 
	var i16 = (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(t))+ uintptr(1)  + uintptr(unsafe.Sizeof(int8(0)))))
	*i16 = int16(10)

	// 偏移int8+1+int16+的字节数,注意这里有个1!!! 
	var i32 = (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(t)) + uintptr(1) + uintptr(unsafe.Sizeof(int8(0))+uintptr(unsafe.Sizeof(int16(0))))))
	*i32 = int32(10)
	fmt.Println(*t)

输出:

10 10 10

附上两个神器:

功能函数
获取对齐值unsafe.Alignof(t.i16)
获取偏移值unsafe.Offsetof(t.i16)

5.如何利用内存对齐来优化golang?

5.1 结构体占用内存过大的问题

根据计算对齐值进行成员顺序的拼凑,可以一定程度上缩小结构体占用的内存。

5.2 指针运算的坑

通过分析偏移量和对齐值,准确计算每个成员所偏移的位数,避免算错。

延伸阅读

Go语言实战笔记(二十六)| Go unsafe 包之内存布局

如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~

以上是关于Golang优化之内存对齐的主要内容,如果未能解决你的问题,请参考以下文章

为 SIMD 分配内存对齐的缓冲区; |16 如何给出 16 的奇数倍数,为啥要这样做?

在 golang 中优化数据结构/字对齐填充

golang 性能优化之累加哈希

Sword 计算机内存对齐

Golang内存对齐

为啥 GCC 不优化结构?