Golang 内存对齐


为什么要内存对齐

CPU访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问。比如32位的CPU,字长为4字节,那么CPU访问内存的单位也是4字节。

这么设计的目的,是减少CPU访问内存的次数,加大CPU访问内存的吞吐量。比如同样读取8个字节的数据,一次读取4个字节那么只需要读取2次

  • 某些处理器只能存取对齐的数据,存取非对齐的数据可能会引发异常
  • 某些处理不能保证在存取非对齐数据的时候的操作是原子操作

Golang内存对齐

分析下面结构体

type S struct {
	A uint32
	B uint64
	C uint64
	D uint64
	E struct{}
}
func main() {
	fmt.Println(unsafe.Offsetof(S{}.E)) // 32
	fmt.Println(unsafe.Sizeof(S{}.E)) // 0
	fmt.Println(unsafe.Sizeof(S{})) // 40
}

8字节对齐

一个非空结构体包含有尾部 size 为 0 的变量(字段),如果不给它分配内存,那么该变量(字段)的指针地址将指向一个超出该结构体内存范围的内存空间。这可能会导致内存泄漏,或者在内存垃圾回收过程中,程序 crash 掉

类型大小

类型大小go官方要求
bool1
byte unit8 int811
unit16 int1622
unit32 int32 float3244
unit64 int64 float64 complex6488
complex1281616
int uint1 word32位架构为4字节,64位架构为8字节
uintptr1 word必须能够存在任一内存地址
string2 words
指针1 word
切片3 words
map1 word
chan1 word
func1 word
interface2 words
struct所有字段大小之和+所有填充的字节数空结构体大小为0
array元素类型大小*元素数量没有元素的数组大小为0
struct{} [0]T{}00

具体代码 结构体优化

64位安全访问保证

在 32 位系统上想要原子操作 64 位字(如 uint64)的话,需要由调用方保证其数据地址是 64 位对齐的,否则原子访问会有异常。

拿uint64来说,大小为 8bytes,32 位系统上按 4 字节 对齐,64 位系统上按 8 字节对齐。在 64 位系统上,8bytes 刚好和其字长相同,所以可以一次完成原子的访问,不被其他操作影响或打断。
而 32 位系统,4byte 对齐,字长也为 4bytes,可能出现uint64的数据分布在两个数据块中,需要两次操作才能完成访问。如果两次操作中间有可能别其他操作修改,不能保证原子性。这样的访问方式也是不安全的

在 32 位系统上,开发者有义务使 64 位字长的数据的原子访问是 64 位(8 字节)对齐的。在全局变量,结构体和切片的的第一个字长数据可以被认为是 64 位对齐的

如何保证呢?
变量或开辟的结构体、数组和切片值中的第一个 64 位字可以被认为是 8 字节对齐
开辟的意思是通过声明,make,new 方式创建的,就是说这样创建的 64 位字可以保证是 64 位对齐的。

References

https://www.jianshu.com/p/67600a8ddb8c
https://xie.infoq.cn/article/594a7f54c639accb53796cfc7


文章作者: 江湖义气
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江湖义气 !
  目录