Go 中通过组织结构体以节省内存

先执行一段示例代码,有一个类似如下的结构体 myStruct

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type myStruct struct {
    myBool  bool
    myFloat float64
    myInt   int32
}

func main() {
    a := myStruct{}

    fmt.Println(unsafe.Sizeof(a.myBool))  // 1
    fmt.Println(unsafe.Sizeof(a.myFloat)) // 8
    fmt.Println(unsafe.Sizeof(a.myInt))   // 4
    fmt.Println(unsafe.Sizeof(a))         // 24

    fmt.Println(reflect.TypeOf(a.myBool).Align())  // 1
    fmt.Println(reflect.TypeOf(a.myFloat).Align()) // 8
    fmt.Println(reflect.TypeOf(a.myInt).Align())   // 4
    fmt.Println(reflect.TypeOf(a).Align())         // 8

    fmt.Println(unsafe.Offsetof(a.myBool))  // 0
    fmt.Println(unsafe.Offsetof(a.myFloat)) // 8
    fmt.Println(unsafe.Offsetof(a.myInt))   // 16
}

通过 Sizeof 输出的大小发现,虽然结构体各字段 bool 占用 1 字节,float64 占用 8 字节,int32 占用 4 字节,但是总共大小却是 24 字节。

在实例化结构体的时候,使用的内存并不是 1 + 8 + 4 = 13 字节, 实际上在分配内存的时候,有一个内存对齐规则,结构体是按最大的类型需要的 8 字节为基础进行内存对齐的分配,超过 8 字节时必须独占,不同的机器可能不同。

所以第一个 bool 字段分配 8 字节的空间,第一个字节放入 myBool,这时 myFloat float64 需要 8 个字节,剩下的 7 个字节不够放,所以 Offset 到下一个 8 字节的空间,于是这两个字段就用了 16 字节,依次类推,所以每个字段都用了 8 字节,所以是 8 + 8 + 8 = 24 字节。

内存分配如图:

|X|O|O|O|O|O|O|O|    bool 分配了8字节的空间 使用1字节
|X|X|X|X|X|X|X|X|    float64 分配了8字节的空间 使用8字节
|X|X|X|X|O|O|O|O|    int32 分配了8字节的空间 使用4字节

如何优化:

内存对齐影响 struct 的大小,struct 的字段顺序影响 struct 的大小。

调整结构体字段的顺序就可以优化,这里将最大字节的类型放到最前面即可:

type myStruct struct {
    myFloat float64
    myBool  bool
    myInt   int32
}

func main() {
    a := myStruct{}

    fmt.Println(unsafe.Sizeof(a.myBool))  // 1
    fmt.Println(unsafe.Sizeof(a.myFloat)) // 8
    fmt.Println(unsafe.Sizeof(a.myInt))   // 4
    fmt.Println(unsafe.Sizeof(a))         // 16

    fmt.Println(reflect.TypeOf(a.myBool).Align())  // 1
    fmt.Println(reflect.TypeOf(a.myFloat).Align()) // 8
    fmt.Println(reflect.TypeOf(a.myInt).Align())   // 4
    fmt.Println(reflect.TypeOf(a).Align())         // 8

    fmt.Println(unsafe.Offsetof(a.myFloat)) // 0
    fmt.Println(unsafe.Offsetof(a.myBool))  // 8
    fmt.Println(unsafe.Offsetof(a.myInt))   // 12
}

此时内存分配方式发生了变化:

第一个 float64 刚好占满 8 个字节的空间,第二个 bool offset到第二个 8 字节空间即第 8+1 个字节存放, 这里第三个 int32 是 offset 到了第 12+1 个字节的位置存放,应该是 int32 自身的内存对齐是4个字节的原因

内存分配变为:

|X|X|X|X|X|X|X|X|    float64 分配了8字节的空间 使用8字节
|X|O|O|O|X|X|X|X|    bool和int32 共用第二段8字节的空间

这样调整结构体的字段顺序后,只需两块连续的8字节就能保存全部字段,只需要16字节。


也可以看看


全国大流量卡免费领

19元月租ㆍ超值优惠ㆍ长期套餐ㆍ免费包邮ㆍ官方正品