图解Go之内存对齐
苗蕾 ( newbmiao)2020.4.2
提纲
了解内存对齐的收益
为什么要对齐
位 bit | 计算机内部数据存储的最小单位 |
字节 byte | 计算机数据处理的基本单位 |
机器字 machine word | 计算机用来一次性处理事务的一个固定长度 |
为什么要对齐
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
为什么对齐 性能差异测试(无效)
不对齐的地址
为什么对齐 性能差异测试(无效)
数据结构对齐 大小保证(size guarantee)
type | size in bytes |
byte, uint8, int8 | 1 |
uint16, int16 | 2 |
uint32, int32, float32 | 4 |
uint64, int64, float64, complex64 | 8 |
complex128 | 16 |
struct{}, [0]T{} | 0 |
数据结构对齐 对齐保证(align guarantee)
type | alignment guarantee |
bool, byte, uint8, int8 | 1 |
uint16, int16 | 2 |
uint32, int32 | 4 |
float32, complex64 | 4 |
arrays | 由其元素(element)类型决定 |
structs | 由其字段(field)类型决定 |
other types | 一个机器字(machine word)的大小 |
数据结构对齐工具
数据结构对齐 举个🌰
数据结构对齐 几个底层数据结构
数据结构对齐 举个特🌰:final zero field
数据结构对齐 重排优化(粗暴方式-按对齐值的递减来重排成员)
-40%
数据结构对齐 重排优化
数据结构对齐 内存对齐检测
github.com/NewbMiao/Dig101-Go/struct_align_demo.go
内存地址对齐
计算机结构可能会要求内存地址进行对齐;也就是说,一个变量的地址是一个因子的倍数,也就是该变量的类型是对齐值。
函数Alignof接受一个表示任何类型变量的表达式作为参数,并以字节为单位返回变量(类型)的对齐值。对于变量x:
内存地址对齐 举个🌰
为什么是[3]uint32, 不是[12]byte
首先在64位系统和32位系统上,uint32能保证是4bytes对齐, 即state1地址是4N:
uintptr(unsafe.Pointer(&wg.state1))%4 == 0
而为保证8位对齐,我们只需要判断state1地址是否为8的倍数
●如果是(N为偶数),那前8bytes就是64位对齐
●否则(N为奇数),那后8bytes是64位对齐
而且剩余的4bytes可以给sema字段用,也不浪费内存
https://github.com/golang/go/issues/19149#issuecomment-347997080
64位字的安全访问保证(32位系统)
在x86-32上,64位函数使用Pentium MMX之前不存在的指令。
在非Linux ARM上,64位函数使用ARMv6k内核之前不可用的指令。
在ARM,x86-32和32位MIPS上,调用方有责任安排对原子访问的64位字的64位对齐。 变量或分配的结构、数组或切片中的第一个字(word)可以依赖当做是64位对齐的。
64位字的安全访问保证 Why?
这是因为int64在bool之后未对齐。
它是32位对齐的,但不是64位对齐的,因为我们使用的是32位系统,
因此实际上只是两个32位值并排在一起。
https://github.com/golang/go/issues/6404#issuecomment-66085602
64位字的安全访问保证 How?
变量或已分配的结构体、数组或切片中的第一个字(word)可以依赖当做是64位对齐的。
The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
已分配:new 或者make
64位字的安全访问保证 How?
64位字的安全访问保证 How?
64位字的安全访问保证 How?
64位字的安全访问保证 How?
64位字的安全访问保证 How?
❌
64位字的安全访问保证 How?
一些源码中的🌰
GMP中的管理groutine本地队列的上下文p中,记录计时器运行时长的uint64,
需要保证32位系统上也是8byte对齐(原子操作)
一些源码中的🌰
堆对象分配的mheap中,管理全局cache的中心缓存列表central,分配或释放需要加互斥锁
另外为了不同列表间互斥锁不会伪共享,增加了cacheLinePadding
cacheLine 参考: https://appliedgo.net/concurrencyslower/
cacheLine引起的伪共享
64位字的安全访问保证 Bug!
如果包含首个64位字的结构体是12byte大小时,不一定能保证64未对齐
这是tinyalloc分配小对象时没有做对齐保证
(可以结合这里回想一下waitGroup.state1为什么不使用first word来保证64位字的安全访问)
64位字的安全访问保证 改为加锁!
总结
Thanks
Go 夜读
菜鸟Miao