什么是内存对齐
type person struct {
age int64
height int64
}
func TestMdemo(t *testing.T) {
fmt.Println(unsafe.Sizeof(person{}))
fmt.Println(person{})
}
此时的 打印值为:16,一个64长度int占8字节,两个就是16。
type person struct {
age int32
height int64
}
func TestMdemo(t *testing.T) {
fmt.Println(unsafe.Sizeof(person{}))
fmt.Println(person{})
}
此时的 打印值为:16,4+8 =12 ,和我们预期不符合。这就是内存对齐现象。
为什么要对齐
那么关于golang如何对齐内存的呢。这里和cpu的访问数据是有关的。
cup访问内存并非按字节读取,而是按照 ”字长“ 来读取
计算机功能的强弱或性能的好坏,不是由某项指标决定的,而是由它的系统结构、指令系统、硬件组成、软件
配置等多方面的因素综合决定的。
计算机以及其相关的软件、硬件设备作为现代科学技术的结晶。应用于工业、商业、金融、教育、科研、军
事、通信及国防建设等国民经济的各个方面。诸多行业的发展对计算机科学愈来愈表现出强烈的依赖性。所以
计算机的评价与检测就十分重要。计算机功能的强弱或性能的好坏,不是由某项指标决定的,而是由它的系统
结构、指令系统、硬件组成、软件配置等多方面的因素综合决定的。
计算机在同一时间内处理的一组二进制数称为一个计算机的“字”,而这组二进制数的位数就是“字长”。在其他
指标相同时,字长越大计算机处理数据的速度就越快。早期的微型计算机的字长一般是8位和16位。586
(Pentium, Pentium Pro, PentiumⅡ,PentiumⅢ,Pentium 4)大多是32位,大多数人都装64位的了。
一般我们现在的服务器都是64位8字节的字长。那么实际上我们cpu读取一个8字节的数据只需要一次读取。
- 1.增加程序的吞吐
cpu读取一个变量是由字长开始读取,那么我读取int64的话只需要读一次,如果按照字节长度来读取的话实际上就不太可行,需要读取8次获取一个数据,操作明显的累赘。但是如果说字长太长实际上又会导致部分的空间浪费,为了利于服务器运行并发程序,需要有一个合适的值。 - 2.利于并发的原子性操作
我们如何来利用cpu天然的利好与并发程序呢,此时字长就很重要。举个简单例子:我有一个公共变量 counter int64,如果我在不加锁的情况下(我并不在乎最后结果如何),多个线程对值进行改变,此时得到的具体值我们并不能估计出来,但是我们可以知道这个值的内存分布式是正确的。也就是一个single mechine word。 所以这个是一个”并发安全“的操作。大家可以想一下,因为这个值每次只能一次性读出来,一次cpu进行修改,所以在多核线程上世纪就有了一个并发”安全“的操作。此处安全是打引号。我不反对但也不建议这么操作,从业务角度来说你肯定不能对值进行预期。
如何对齐 (对齐系数)
Size and alignment guarantees
type size in bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
保证”结构体“最小对齐
For a variable x of any type: unsafe.Alignof(x) is at least 1.
For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe
.Alignof(x.f) for each field f of x, but at least 1.
For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array's element type.
- 任意变量的最小align 系数 >=1
- 任意结构体变量 ,align系数是其实 struct 中所有参数 align 值的最大一位
- 对于数组变量:就是单个数组值的align值
其实掌握了align 系数算法我们可以轻松算出struct 的size
利用对齐系数计算结构体大小
我们首先背下来官网的三个原则,就是上面写好了的。
- 一般来说一个非结构体 的值,string不算,底层是字节数组,它的align =size
- 一个结构体的align = align_max(struct.filed)
- align值最小为1
我们来简单计算一下
type person struct {
i int32
i1 int32
}
size = size(i) +size(i1) : 8
align = align(i)/align(i1) :4 此时因为两个值的align相同
type person struct {
i int32
i1 int32
i2 int64
}
size = 16 此时应该是size i +size i1 +size i2 = 4+4+8 =16
align = align(i2):8 此时因为两个值的align相同
type person struct {
i int32
i1 int64
}
size = 16 此时应该是size i +size i1 = 4+8 =12 ,但是应该size是 align 的倍数,所以最近就是16
align = align(i2):8 此时因为两个值的align相同
计算中的特殊情况
type person struct {
i int32
a struct{}
}
size = 8
align = align(i2):4 此时取值align(4)
这里我们需要特殊说明一下情况:假设a 字段为空,那么如果这个结构体有个方法是返回a,具体如下
type person struct {
i int32
a struct{}
}
func (a *person)ted()interface{}{
return a.a
}
这种情况就有可能出现内存泄露的情况。