Go 内存对齐

147 阅读3分钟

Golang 内存对齐

  • 对齐的目的: 方便 CPU 进行快速的读取数据,通过空间换时间。

  • 对齐系数: 32 位系统对齐系数是 464 位系统对齐系数是 8

unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数。和 Sizeof 类似, Alignof 也是返回一个常量表达式,对应一个常量。通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节),其它的类型对齐到机器字大小。

package main

import (
  "fmt"
  "unsafe"
)

func main() {
  fmt.Printf("int alignof is %d\n", unsafe.Alignof(int(0))) // 8
  fmt.Printf("int32 alignof is %d\n", unsafe.Alignof(int32(0))) // 4
  fmt.Printf("int64 alignof is %d\n", unsafe.Alignof(int64(0))) // 8
  fmt.Printf("bool alignof is %d\n", unsafe.Alignof(bool(true))) // 1
}
  • 如下是对应类型和所占用的字节
类型大小
bool1个字节
intN, uintN, floatN, complexNN/8个字节(例如float64是8个字节)
int, uint, uintptr1个机器字
*T1个机器字
string2个机器字(data、len)
[]T3个机器字(data、len、cap)
map1个机器字
func1个机器字
chan1个机器字
interface2个机器字(type、value)

对齐的原则

由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐。

  • 字段对齐规则:字段的起始地址必须是其对齐单位的整数倍
    • 每个字段只需要满足自己的对齐要求,不是为后续字段做对齐
  • 结构体对齐规则:结构体的总大小必须是最大对齐单位的整数倍。
package main

import (
  "fmt"
  "runtime"
  "unsafe"
)

type User struct {
  IsMan bool  // 1 byte
  Age   int16 // 2 byte
}

type User2 struct {
  Height int8  // 1 byte
  Money  int64 // 8 byte
  Age    int32 // 4 byte
}

type User3 struct {
  Height int8  // 1 byte
  Age    int32 // 4 byte
  Money  int64 // 8 byte
}

func main() {
  fmt.Println(runtime.GOARCH) // amd64
  u := User{}
  fmt.Println(unsafe.Sizeof(u)) // 4 bytes

  u2 := User2{}
  fmt.Println(unsafe.Sizeof(u2)) // 24 bytes

  u3 := User3{}
  fmt.Println(unsafe.Sizeof(u3)) // 16 bytes
}

详解结构体中的内存布局情况

  • u1

image.png

  • u2

image.png

u3 内存对齐详细说明如下

  • Height: 占用 1 字节,从地址 0 开始;按照字段本身的对齐规则,Height 占据内存地址 [0:1];必须填充到 4 字节(因为下一个字段 Age 需要 4 字节对齐,其填充地址必须从 4 的整数倍开始)故需要填充 3 个字节,因此占用 [0:4]
  • Age: 占用 4 字节,从地址 4 开始,因此占用 [4:8]
  • Money: 占用 8 字节,从地址 8 开始(已经对齐到 8 字节)无需填充,因此占用 [8:16]
  • 最大对齐单位是 8 字节(由 Money 决定),总大小已经是 16 字节,无需额外填充

image.png

参考