各种数据结构的内存占用
基础类型
按照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
|
其中需要注意的是string
和any
。在src/reflect/value.go
中,string
的底层存储结构:
1 2 3 4
| type StringHeader struct { Data uintptr Len int }
|
string
由一个指针和长度两个字段组成,uintptr
是8byte
,int
是8byte
,合起来是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
。
通过地址值换算,可以算出来原先1byte
的age
实际占用8byte
,应该占1byte
的sub
实际占用8byte
。
这是因为结构体中存在内存对齐。内存对齐的目的是为了CPU访问数据效率更高。
因为 CPU 访问内存时,并不是逐个字节访问,而是以字(word)为单位访问。比如 64位CPU的字长(word size)为8bytes,那么CPU访问内存的单位也是8字节,每次加载的内存数据也是固定的若干字长,如8words(64bytes)、16words(128bytes)等。
此时会根据对齐系数进行内存调整
规则如下:
- 对于任意类型的变量 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{}
,作为模拟信号