Golang中结构体内存分配

各种数据结构的内存占用

基础类型

按照src/builtin/builtin.go中的顺序,查看不同类型的数据占用内存,单位byte

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
29
30
fmt.Println(unsafe.Sizeof(bool(false)))            1

fmt.Println(unsafe.Sizeof(uint(0))) 8

fmt.Println(unsafe.Sizeof(uint8(0))) 1
fmt.Println(unsafe.Sizeof(uint16(0))) 2
fmt.Println(unsafe.Sizeof(uint32(0))) 4
fmt.Println(unsafe.Sizeof(uint64(0))) 8

fmt.Println(unsafe.Sizeof(int8(0))) 1
fmt.Println(unsafe.Sizeof(int16(0))) 2
fmt.Println(unsafe.Sizeof(int32(0))) 4
fmt.Println(unsafe.Sizeof(int64(0))) 8

fmt.Println(unsafe.Sizeof(float32(0))) 4
fmt.Println(unsafe.Sizeof(float64(0))) 8

fmt.Println(unsafe.Sizeof(complex64(0 + 0i))) 8
fmt.Println(unsafe.Sizeof(complex128(0 + 0i))) 16

fmt.Println(unsafe.Sizeof(string(""))) 16

fmt.Println(unsafe.Sizeof(int(0))) 8
fmt.Println(unsafe.Sizeof(uint(0))) 8
fmt.Println(unsafe.Sizeof(uintptr(0))) 8

fmt.Println(unsafe.Sizeof(byte(0))) 1
fmt.Println(unsafe.Sizeof(rune(0))) 4

fmt.Println(unsafe.Sizeof(any(0))) 16

其中需要注意的是stringany。在src/reflect/value.go中,string的底层存储结构:

1
2
3
4
type StringHeader struct {
Data uintptr
Len int
}

string由一个指针和长度两个字段组成,uintptr8byteint8byte,合起来是16byte

any的底层存储结构

1
2
3
4
5
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}

一个类型指针,一个数据,*rtype指针变量,8byte,unsafe.Pointer也是一个指针类型变量,8byte,合起来是16byte

1
type Pointer *ArbitraryType

组合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fmt.Println(unsafe.Sizeof([2]int{}))                16
fmt.Println(unsafe.Sizeof([3]int{})) 24
fmt.Println(unsafe.Sizeof([2]float32{})) 8
fmt.Println(unsafe.Sizeof([2]string{})) 32

fmt.Println(unsafe.Sizeof([]int{})) 24
fmt.Println(unsafe.Sizeof([]float32{})) 24
fmt.Println(unsafe.Sizeof([]string{})) 24

fmt.Println(unsafe.Sizeof(map[int]int{})) 8
fmt.Println(unsafe.Sizeof(map[string]string{})) 8

ch1 := make(chan int)
fmt.Println(unsafe.Sizeof(ch1)) 8
ch2 := make(chan int, 10)
fmt.Println(unsafe.Sizeof(ch2)) 8
var ch3 chan string
fmt.Println(unsafe.Sizeof(ch3)) 8

var f1 func()
fmt.Println(unsafe.Sizeof(f1)) 8

var i interface{}
fmt.Println(unsafe.Sizeof(i)) 16

可以看到,数组这种复合类型的内存占用取决于内部元素类型和数组长度,类型占用*长度

切片的底层数据结构可以在src/reflect/value.go看到

1
2
3
4
5
type SliceHeader struct {
Data uintptr
Len int
Cap int
}

由一个指针,两个int类型组成,就是24byte

map、channel、function,底层都是指针,即使是nil,也是占8byte

空interface,由类型和指针组成,占16byte

结构体

结构体中的内存存储是连续的,但是具体每个字段占用内存大小就要看字段本身占内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type User2 struct {
name string // 16byte
age int8 // 1 byte
num int64 // 8 byte
sub bool // 1 byte
last string // 16 bit
}

u2 := User2{
name: "mitaka",
age: 18,
num: 228851111111,
sub: false,
last: "abc",
}
fmt.Printf("name size: %d\n", unsafe.Sizeof(u2.name)) // 16
fmt.Printf("age size: %d\n", unsafe.Sizeof(u2.age)) // 1
fmt.Printf("num size: %d\n", unsafe.Sizeof(u2.num)) // 8
fmt.Printf("sub size: %d\n", unsafe.Sizeof(u2.sub)) // 1
fmt.Printf("last size: %d\n", unsafe.Sizeof(u2.last)) // 16
fmt.Printf("u2 size: %d\n", unsafe.Sizeof(u2)) // 56 ???
fmt.Printf("u: %p,u.name: %p,u.age: %p,u.num: %p,u.sub: %p,last: %p\n", &u2, &u2.name, &u2.age, &u2.num, &u2.sub, &u2.last)
// u: 0x1400012a040,u.name: 0x1400012a040,u.age: 0x1400012a050,u.num: 0x1400012a058,u.sub: 0x1400012a060,last: 0x1400012a068

可以看到,u2占用的内存大小,应该是16+1+8+1+16 = 42byte,但是实际是56byte

通过地址值换算,可以算出来原先1byteage实际占用8byte,应该占1bytesub实际占用8byte

这是因为结构体中存在内存对齐。内存对齐的目的是为了CPU访问数据效率更高。

因为 CPU 访问内存时,并不是逐个字节访问,而是以字(word)为单位访问。比如 64位CPU的字长(word size)为8bytes,那么CPU访问内存的单位也是8字节,每次加载的内存数据也是固定的若干字长,如8words(64bytes)、16words(128bytes)等。

此时会根据对齐系数进行内存调整

1
unsafe.Alignof(u2) //  8

规则如下:

  • 对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
  • 对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f)unsafe.Alignof(x) 等于其中的最大值。
  • 对于 array 类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。

对齐之后

1
2
3
4
5
6
7
type User2 struct {
name string // 16byte
age int8 // 8 byte
num int64 // 8 byte
sub bool // 8 byte
last string // 16 bit
}

空结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var k1 struct{}
var k2 struct{}
fmt.Println(k1 == k2) true
fmt.Printf("k1: %p,k2:= %p\n", &k1, &k2) // k1: 0x104ddafe8,k2:= 0x104ddafe8
fmt.Printf("k1 size: %d,k2 size:= %d\n", unsafe.Sizeof(k1), unsafe.Sizeof(k2))
fmt.Println(unsafe.Sizeof(struct{}{})) 0
fmt.Println(unsafe.Sizeof([0]int{})) 0

fmt.Println(unsafe.Sizeof(struct { 1
s struct{}
n int8
}{}))
fmt.Println(unsafe.Sizeof(struct { 2
n int8
s struct{}
}{}))

空结构体有地址,全局的空结构体指向同一个地址,占用内存是0。

[o]T,0长度的数组,占用空间是0。

1
2
3
4
5
6
7
8
9
10
11
// 这个结构体内存占1byte,s是空结构体,不占内存,n占1byte
struct {
s struct{}
n int8
}

// 这个结构体内存占2byte,n占1byte,为了避免内存泄露会额外进行一次内存对齐。
struct {
n int8
s struct{}
}

不占内存的空结构体,用法可以这样:

map[int]struct{},作为map的value,用来验证key是否存在

chan struct{},作为模拟信号