内存对齐

Go语言中数据类型的大小(字节数)取决于具体类型和平台架构(32位或64位)。以下是Go语言主要数据类型的字节占用情况(以64位系统为主):

0.1 基本数据类型字节占用表

数据类型
32位系统
64位系统
说明

​bool​

1字节

1字节

布尔值

​byte​

1字节

1字节

uint8别名

​rune​

4字节

4字节

int32别名,表示Unicode码点

​int​

4字节

8字节

平台相关整数

​uint​

4字节

8字节

平台相关无符号整数

​int8/uint8​

1字节

1字节

8位整数

​int16/uint16​

2字节

2字节

16位整数

​int32/uint32​

4字节

4字节

32位整数

​int64/uint64​

8字节

8字节

64位整数

​float32​

4字节

4字节

单精度浮点数

​float64​

8字节

8字节

双精度浮点数

​complex64​

8字节

8字节

两个float32组成

​complex128​

16字节

16字节

两个float64组成

​指针类型​

4字节

8字节

指向内存地址

​uintptr​

4字节

8字节

足够容纳指针值的整数

0.2 复合数据类型字节占用

0.2.1 数组 (Array)

数组大小固定,计算公式:

示例:

0.2.2 结构体 (Struct)

结构体大小是所有字段大小之和,但需要考虑​​内存对齐​​:

0.2.3 切片 (Slice)

切片是24字节的复合结构(64位系统):

示例:

0.2.4 字符串 (String)

字符串是16字节的复合结构(64位系统):

示例:

0.2.5 映射 (Map)

映射是一个指针(8字节),指向运行时实现的哈希表结构:

0.2.6 通道 (Channel)

通道是一个指针(8字节),指向运行时实现的通道结构:

0.2.7 接口 (Interface)

接口是16字节的复合结构(64位系统):

示例:

0.3 内存对齐规则

内存对齐(Memory Alignment)是指数据在内存中的存储位置必须满足特定地址边界的要求。在 Go 语言中,内存对齐遵循以下基本原则:

  1. ​数据类型对齐系数(Align)​​:

    • 每种数据类型都有其自然对齐系数(通常等于其大小)

    • 对齐系数必须是 2 的幂(1, 2, 4, 8, 16...)

  2. ​结构体对齐规则​​:

    • 结构体的对齐系数是其所有字段中最大的对齐系数

    • 结构体大小必须是其对齐系数的整数倍

  3. ​字段存储规则​​:

    • 字段的偏移量必须是其类型对齐系数的整数倍

    • 编译器会在字段间插入填充字节(padding)以满足对齐要求

0.4 Go 中的基本类型对齐系数

数据类型
大小(字节)
对齐系数

bool

1

1

int8, uint8, byte

1

1

int16, uint16

2

2

int32, uint32, float32, rune

4

4

int64, uint64, float64, complex64

8

8

complex128

16

16

string

16

8

slice

24

8

map

8

8

chan

8

8

interface{}

16

8

指针(*T

8

8

还有一种特殊的类型,空结构体,一般来说空结构体大小为0,不需要额外填充,但如果该空结构体被外部指针指向,如果不填充就会导致指向结构体外的内存地址,这样会在结构体回收的时候导致指针悬垂,如果该内存地址被复用,可能导致内存安全问题。

所以 GO 会默认直接在结构体末尾的空结构体填充 pedding

0.5 如何查看类型大小

使用unsafe.Sizeof()函数:

0.6 如何查看对齐系数

unsafe 标准库提供了 Alignof 方法,可以返回一个类型的对齐值,也可以叫做对齐系数或者对齐倍数。例如:

0.7 重要注意事项

  1. ​指针大小​​:在32位系统上为4字节,64位系统上为8字节

  2. ​切片大小​​:24字节(64位)只包含切片头,不包含底层数组

  3. ​字符串大小​​:16字节(64位)只包含字符串头,不包含底层字节数组

  4. ​零值大小​​:unsafe.Sizeof(nil)无效,但空接口大小相同

  5. ​函数大小​​:函数值在Go中是8字节(指向函数代码的指针)

0.8 为什么需要内存对齐

0.8.1 硬件要求

  • ​CPU访问优化​​:现代CPU从内存读取数据时,通常以字长(word size)为单位(64位系统为8字节)

  • ​对齐访问​​:CPU访问对齐的内存地址只需单次操作,访问未对齐地址可能需要多次操作

    • 通过移位和掩码操作拼接所需数据,比单纯的位提取操作更耗时

  • ​硬件限制​​:某些架构(如ARM)直接拒绝未对齐访问,导致程序崩溃

0.8.2 性能提升

  • ​减少内存访问​​:对齐数据可减少CPU访问内存的次数

  • ​缓存效率​​:对齐数据能更好地利用CPU缓存行(通常64字节)

    • 当CPU从主内存读取数据时,并不是只读取需要的数据,而是读取一个缓存行大小的数据块(通常为64字节)。

    • 同样,写入缓存也是以缓存行为单位。

    • 如果数据对齐,那么一个缓存行可以容纳更多的有效数据。相反,如果数据未对齐,可能需要两个缓存行来存储一个本可以放在一个缓存行中的数据。可以提高数据加载效率。

  • ​向量化优化​​:SIMD指令(如AVX)要求数据严格对齐

0.8.3 原子操作保证

  • Go的sync/atomic包要求64位变量必须8字节对齐

  • 未对齐的64位变量在32位系统上会导致panic

0.8.4 减少内存碎片

  • 对齐的内存分配更高效,减少内存碎片

  • 内存分配器(如Go的TCMalloc)基于对齐块工作

0.9 最佳实践

  1. ​字段排序优化​​:

    • 按对齐系数降序排列字段(大字段在前)

    • 将相同类型字段放在一起

  2. ​避免过度填充​​:

  1. ​敏感数据对齐​​:

    • 对性能关键的结构体使用//go:align指令强制指定对齐系数

    • 必须是2的幂

  1. ​原子变量声明​​:

    • 使用单独声明确保64位变量对齐

最后更新于