内存对齐

内存对齐是为了cpu更高效访问内存中数据

struct的对齐是:如果类型 t 的对齐保证是 n,那么类型 t 的每个值的地址在运行时必须是 n 的倍数。uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

struct内字段如果填充过多,可以尝试重排,使字段排列更紧密,减少内存浪费

零大小字段要避免作为struct最后一个字段,会有内存浪费

32位系统上对64位字的原子访问要保证其是8bytes对齐的;当然如果不必要的话,还是用加锁(mutex)的方式更清晰简单

如果WaitGroup嵌套到别的结构体时,如果不放到结构体首位会有问题, 这会使其使用受限

合理的字段顺序使struct更紧密,更省内存

type T1 struct {
    a [2]int8
    b int64
    c int16
}
type T2 struct {
    a [2]int8
    c int16
    b int64
}
fmt.Printf("arrange fields to reduce size:\n"+
    "T1 align: %d, size: %d\n"+
    "T2 align: %d, size: %d\n",
    unsafe.Alignof(T1{}), unsafe.Sizeof(T1{}),
    unsafe.Alignof(T2{}), unsafe.Sizeof(T2{}))
/*
output:
arrange fields to reduce size:
T1 align: 8, size: 24
T2 align: 8, size: 16
*/

/*
以64位系统为例,分析如下:
T1,T2内字段最大的都是int64, 大小为8bytes,对齐按机器字确定,64位下是8bytes,所以将按8bytes对齐
T1.a 大小2bytes,填充6bytes使对齐(后边字段已对齐,所以直接填充)
T1.b 大小8bytes,已对齐
T1.c 大小2bytes,填充6bytes使对齐(后边无字段,所以直接填充)
总大小为 8+8+8=24
T2中将c提前后,a和c总大小4bytes,在填充4bytes使对齐
总大小为 8+8=16
*/

零大小字段不要放在最后

type T1 struct {
    a struct{}
    x int64
}

type T2 struct {
    x int64
    a struct{}
}
a1 := T1{}
a2 := T2{}
fmt.Printf("zero size struct{} in field:\n"+
    "T1 (not as final field) size: %d\n"+
    "T2 (as final field) size: %d\n",
    // 8
    unsafe.Sizeof(a1),
    // 64位:16;32位:12
    unsafe.Sizeof(a2))

内存地址对齐

unsafe包规范中说明如果类型t的对齐保证是n,那么类型t的每个值的地址在运行时必须是n的倍数

一个典型示例是

type WaitGroup struct {
  noCopy noCopy
  state1 [3]uint32
}

// state returns pointers to the state and sema fields stored within wg.state1.
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
  // 判定地址是否8位对齐
  if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
    // 前8bytes做uint64指针statep,后4bytes做sema
    return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]
  } else {
    // 后8bytes做uint64指针statep,前4bytes做sema
    return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]
  }
}

如果是8位地址对齐前8bytes做uint64指针statep,后4bytes做sema 如果不是8位地址对齐(4为地址对齐)则选择前4byte做sema,后8字节做uint64指针statep

可是为什么要在32位系统上也要保证一个64位对齐的uint64指针呢? 答案是,为了保证在32位系统上也能原子访问64位对齐的64位字。

64位字安全访问保证

而32位系统,4byte对齐,字长也为4bytes,可能出现uint64的数据分布在两个数据块中,需要两次操作才能完成访问。

如果两次操作中间有可能别其他操作修改,不能保证原子性。

变量或开辟的结构体、数组和切片值中的第一个64位字可以被认为是8字节对齐

参考

Dig101-Go之聊聊struct的内存对齐

最后更新于

这有帮助吗?