unsafe和在Go中的内存对齐

unsafe包和在Go中的内存对齐

unsafe包

unsafe包提供了一些跳过Go语言类型安全限制的操作 其中包括两种类型和三个函数

 type AribitraryType  //可以是任何一种类型
 例如:
 type AribitraryType int //int类型
 ​
 指针 Go语言指针只支持取地址和解引用 不支持运算
 type Pointer *ArbitraryType
 ​
 Pointer表示任意类型的指针
 1.任意类型指针可以转换为Pointer类型值
 2.一个pointer可以转换为任意类型的指针
 ​
 ​
 ​
 func Sizeof(v ArbitraryType) uintptr
 ​
 //返回类型v本身数据所占用的字节数,注意这里的返回值是顶层数据占有的字节数
 比如v如果是一个切片,那么它会返回该切片描述符的大小,而非是该切片底层引用内存的大小
 ​
 ​
 func Aligof(v ArbitraryType) uintptr//返回类型v的对齐方式(在内存中占用的字节数),如果是结构体类型的字段的形式,它会返回字段f在该结构体中的对齐方式
 ​
 ​
 func Offsetof(v ArbitraryType) unitptr
 //返回类型v所代表的结构体字段在结构体中的偏移量,v必须是结构体类型的字段形式

 

内存对齐

很早以前就存在一个问题,为什么将元素一个个append进切片,那么是满足cap*2规律,而一次性append多个数就不满足呢?这就要提到内存对齐了

在cpu中内存是一块一块读取,而不是一个个二进制字节数组读取的,块的大小可以为2、4、6、8等,块的大小被我们称为内存访问粒度unsafe和在Go中的内存对齐unsafe和在Go中的内存对齐内存对齐的优点:对于为对齐的内存,将会导致cpu进行两次内存访问,并且需要消耗多余的时间和资源进行对齐运算,如果是已经对齐的内存,那么cpu仅需要一次访问就可以完成读取动作

对齐规则

  • 结构体的成员变量,第一个成员变量的偏移量为0,往后的每个成员的对齐值必须为编译器默认对齐长度或者当前成员变量类型的长度,取二者的较小值为对齐值,而偏移量必须为对齐值的

  • 结构体本身的对齐值必须为编译器默认对其长度或者结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值

 type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
 }
 //1+4+1+8+1=15 ?
 ​
 但是如果我们用sizeof函数测试
 ​
 fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
 //part1 size: 32, align: 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("EDDYCJY"))
    fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
 }
 //bool align: 1
 int32 align: 4
 int8 align: 1
 int64 align: 8byte align: 1string align: 8
 map align: 8
 ​
 在Go中可以调用unsafe.Alignof来返回相应类型的对其系数 这些系数是2^n,最大不会超过八。这是因为一般编译器的默认对齐系数是8
 ​
 在上述结构体中,每个结构体中的变量要做字节对齐,而最终结果的结构体也需要做字节对齐
 ​
 ​
 ​
 ​
 type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
 }
 // a bool 占用1 对齐到4
 // b int32 占用8 不对齐
 // c int8 占用9 对齐到16
 // d int64 占用24 不对齐
 // e byte 占用25 对齐到32  
 // 总占用32
 ​
 ​
 ​
 type Part2 struct {
    e byte
    c int8
    a bool
    b int32
    d int64
 }
 // e byte 占用1 不对齐
 // c int8 占用2 不对齐‘
 // a bool 占用3 对齐到4
 // b int32 占用8 不对齐
 // d int64 占用16 不对齐
 //总占用16

简单改变一下结构体成员的字段顺序就改变了结构体占用大小

上一篇:Go语言map底层源码理解(map的扩容)


下一篇:screen,client,page三种确定鼠标坐标的区别和原生JS事件写法,区别于Jquery的$.on(x,y);和$.click()