内存对齐

内存对齐

2023.10.01

介绍

参考:【Golang】这个内存对齐呀!?

Go语言中的内存对齐是一种用于优化内存访问和提高性能的技术。内存对齐是计算机体系结构中的一个重要概念,它确保数据结构中的字段在内存中按照一定的规则排列,以便CPU能够更有效地访问这些数据。在Go中,内存对齐通常是由编译器和运行时系统来处理的,而不需要手动控制。以下是关于Go语言中内存对齐的一些详细信息:

  1. 原始数据类型的内存对齐:

    • 在Go语言中,原始数据类型(如int、float、bool等)的内存对齐通常按照它们的大小进行,例如int32会按照4字节对齐,int64会按照8字节对齐。这意味着它们将始终从内存的4字节或8字节边界开始存储。
  2. 结构体的内存对齐:

    • 在Go语言中,结构体的字段也会根据其大小进行内存对齐。通常,结构体字段的对齐方式是根据字段中最大的对齐值来确定的。例如,如果一个结构体有一个int32(对齐4字节)和一个float64(对齐8字节)字段,那么它的对齐方式将按照8字节对齐,以适应float64的大小。
  3. 对齐规则:

    • Go语言的内存对齐规则通常是平台相关的,因为不同的操作系统和体系结构可能有不同的要求。编译器和运行时系统会根据目标平台的要求来确定数据结构的内存布局和对齐方式。
  4. 结构体字段的对齐控制:

    • 在Go语言中,可以使用struct标签来控制结构体字段的对齐方式。例如,可以使用struct标签中的align选项来指定字段的对齐方式,但这通常不是必要的,除非你有特定的对齐需求。
  5. 内存对齐的性能影响:

    • 内存对齐可以提高内存访问的性能,因为它允许CPU更有效地加载和存储数据。不正确的内存对齐可能会导致性能下降,因为它会增加数据访问的开销。

总之,Go语言中的内存对齐是一个重要的底层概念,但通常不需要手动干预。Go的编译器和运行时系统会负责处理内存对齐,以确保代码在不同平台上具有良好的性能和可移植性。如果你对特定平台的内存布局有特殊要求,可以考虑使用struct标签来控制对齐方式,但大多数情况下,Go会自动处理这些细节。

结构体中的使用

结构体中可以利用内存对齐,来减少空间的占用。

1.将字段按照大小顺序排列:将结构体字段按照它们的大小进行排序,这可以减少内存浪费。较小的字段放在前面,较大的字段放在后面。

1
2
3
4
5
goCopy codetype MyStruct struct {
    Field1 int32
    Field2 float64
    Field3 string
}

2.使用struct标签来调整字段对齐:虽然Go不提供直接的字段顺序控制,但你可以使用struct标签来调整字段的对齐方式,例如,使用align选项来指定对齐方式。

1
2
3
4
5
goCopy codetype MyStruct struct {
    Field1 int32  `struct:"align:4"`
    Field2 float64
    Field3 string
}

请注意,尽管你可以使用上述方法来尝试优化内存布局,但在大多数情况下,Go的编译器和运行时系统已经足够智能,能够自动执行内存对齐和布局的优化。因此,通常情况下不需要手动控制内存对齐,而应该专注于编写可读性和维护性更好的代码,让Go编译器来处理底层的内存管理细节。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type MyStruct1 struct {
    a int8
    b string // 占用16字节
    c int8
}

type MyStruct2 struct {
    a int8
    c int8
    b string // 占用16字节
}

func main() {
    s11 := MyStruct1{
       a: 1,
       b: "hello",
       c: 1,
    }

    s21 := MyStruct2{
       a: 1,
       b: "hello",
       c: 1,
    }

    fmt.Println(unsafe.Sizeof(s11)) // 32
    fmt.Println(unsafe.Sizeof(s21)) // 24
}

在示例中,MyStruct1 结构体的字段顺序是 a int8, b string, c int8。对于大多数的平台,int8 的大小是1字节,而 string 类型包括一个指向底层数据的指针和一个长度字段,大小为16字节(64位系统)。

在大多数64位系统上,string 的内存对齐通常是8字节,因为这些系统的指针大小通常是8字节。在32位系统上,string 的内存对齐通常是4字节,因为32位系统的指针大小通常是4字节。 可以使用unsafe.Sizeof()来查看变量内存占用情况,使用unsafe.Alignof()来查看变量的内存对齐情况。

1
2
3
var s string
size := unsafe.Sizeof(s)  // 16
align := unsafe.Alignof(s)  // 8

首先,让我们计算每个字段的大小:

  1. a int8:1字节
  2. b string:通常8字节(指针) + 8字节(长度) = 16字节
  3. c int8:1字节

然后,考虑内存对齐的要求。在大多数平台上,内存对齐要求是按照字段的大小将其对齐到某个倍数。通常,int8 对齐到1字节,string 在64位系统上对齐到8字节。

现在,让我们计算 MyStruct1 结构体的总大小:

  1. a 需要1字节。
  2. b 需要16字节。
  3. c 需要1字节。

但由于string会对齐到8字节,所以变量a之后会空出7字节的内存,然后才能存放变量b

现在,将这些字段的大小相加:8 + 16 + 1 = 25 字节。但是,由于内存对齐的要求,Go 编译器会将结构体的大小舍入到最接近的8字节(因为结构体中字段中最大对齐值为8)的倍数。所以,MyStruct1 结构体的实际大小是32字节,而不是25字节。

因此,MyStruct1 结构体的占用内存为32字节

MyStruct2 结构体的字段顺序是 a int8, c int8, b string。 内存占用情况:变量a和变量c分别占用1个字节(共2字节),变量b内存对齐到8字节,因此变量c后面会空出6个字节的内存,然后才能存放变量b。 因此,MyStruct1 结构体的占用内存为:8+16=24 字节。(已满足结构体的内存对齐值8)

0%