GO对象对齐-怎么轻松降低内存占用

我们先看下面的代码

var A = struct {
		oneByte byte
		anotherByte byte
		oneInt  int
	}{}
var B = struct {
		oneByte byte
		oneInt  int
		anotherByte byte
	}{}

看起来这两个变量包含的字段一模一样的,都是两个byte和一个int,那么他们的大小相同吗?
我们不妨使用reflect包检查一下,代码如下图

typeA := reflect.TypeOf(A)
typeB := reflect.TypeOf(B)
fmt.Println("A size", typeA.Size())
fmt.Println("B size", typeB.Size())

返回结果
返回结果
奇也怪哉,我们只是调换了下字段顺序而已,为什么会有这样的差异?
为了解释这个问题,我们需要理解一点-对齐
什么是对齐呢,就是说内存布局中,两个字段之间不一定是紧紧贴着的,而是可能会留下一个空档,这是因为我们的操作系统在获取值的时候,并不是像我们想象中一样一个字节一个字节的获取,而是可能按照2,4,8个字节(不同系统不一样)获取一个块。
那么我们不妨想象一下, 假设一个int被放置在1的位置,占用了1,2,3,4个内存块,而某操作系统是按照4个字节开始获取,他会怎么得到这个int变量呢?
他会首先获取到0 1 2 3四个字节的块,然后再获取4 5 6 7的块,最后剪切拼贴成这个int;反过来如果int直接就位于4这个起点,系统就只需要1次获取,中间的花销差距可以想象。
更何况,很多操作系统甚至不支持这种操作,也就是说如果你索取上述这么个变量,系统就罢工了
为了应对上述的情形,go于是搬出了类型对齐保证这么个概念,也就是说go对于若干种类型,都设置了他的对齐保证,确保该类型存储的起始地址是这个指定值的倍数,想想看,对于上面int的处理,如果我们能保证int永远从4的倍数开始,不就可以很好地解决这个问题吗?
类型对齐保证解决了这个“无法或者不能高效获取值”的问题,但是这样却引入了一个新的问题,我们知道go中结构体的内存布局是依赖于字段的,也就是说字段声明的时候顺序是abc,那么内存布局中就会是abc。
试想一下,如果b的“类型对齐保证”是8字节,而a的大小只有1字节(比方说是boolean),那么ab之间就得空出7个字节,因此占用了16个字节(而不是9字节)。
问题还不仅仅如此,比方说对于刚刚的问题,我们如果尝试改为ba顺序,我们会发现结果仍然是16字节,这是因为结构体本身也有类型对齐保证(一般等于字段的最大类型对齐保证),而a填充完之后,结构体因为自身类型对齐保证是8字节,于是会乖乖的在后面空出七个字节,于是我们并没有起到效果。
总的来说,我们会发现字段的顺序其实会影响到内存的大小,那么我们有没有什么比较好的办法去处理来提高内存使用呢?
很简单,我们可以将字段从占用字节数大到小排序,就可以尽量减少内存空洞了。
如下图所示
网图展示

可以看到在当前的布局下,第一层和第三层并没有得到很好地利用,如果我们调整顺序,就可以实现下面的效果网图展示2
当然一切的前提在于我们需要知道该类型的占用大小和类型对齐大小,我们可以用reflect.TypeOf(T).Align()来获取一般类型对齐大小,用reflect.TypeOf(T).FieldAlign()来获取字段类型对齐大小,在官方编译器中,二者应该是一致的,我们还可以用reflect.TypeOf(T).Size()来获取大小
当然其实我们也可以根据下述表格查询

type                                 size in bytes

byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

初次之外,对于结构体来说,其对齐保证大小是字段的对齐保证的最大值,同时至少为1(即便是空的)
而对于数组来说,对齐大小等同于其中一个元素的对齐保证大小

发布了31 篇原创文章 · 获赞 32 · 访问量 749

猜你喜欢

转载自blog.csdn.net/a348752377/article/details/103060611
今日推荐