0x00 内存管理
go变量声明存在两部分:
- Heap: 全局存储空间, 由runtime来管理,这部分区域是可以共享的, 生命周期由gc来管理, 对象可以被其他函数访问到(通过指针方式)
- Stack: 局部变量存储,生命周期由对应的函数来管理,并且只能被这个函数内部来访问到,没有gc。
一般放在stack里面性能要比heap速度快的多得多,所以尽可能的将对象放到stack里面
而go逃逸是在编译阶段分析对象,当一个在stack里面声明的一个variable试图被分配到heap里面,这就发生了逃逸,逃逸分析的目的是为了尽可能的将对象分配在stack里面,而不是heap上,这两者性能相差非常大
分配在stack里面的variable是由function本身来管理,而分配在heap上面的对象是由runtime来管理,这个里面还涉及到一些列的管理机制
测试例子

测试结果

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import "testing"
type T struct { X [10 * 1000 * 1000]byte }
var global interface{}
func BenchmarkAllocOnHeap(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { global = &T{} } }
func BenchmarkAllocOnStack(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { local := T{} _ = local } }
|
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 31 32 33
| ➜ demo go test -cpu 1,2,4,8,16 -bench=. demo_test.go goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz BenchmarkAllocOnHeap 1467 815408 ns/op 10002432 B/op 1 allocs/op BenchmarkAllocOnHeap-2 1532 810348 ns/op 10002432 B/op 1 allocs/op BenchmarkAllocOnHeap-4 1410 834213 ns/op 10002434 B/op 1 allocs/op BenchmarkAllocOnHeap-16 1410 862156 ns/op 10002435 B/op 1 allocs/op BenchmarkAllocOnStack 1000000000 0.3225 ns/op 0 B/op 0 allocs/op BenchmarkAllocOnStack-2 1000000000 0.3128 ns/op 0 B/op 0 allocs/op BenchmarkAllocOnStack-4 1000000000 0.3148 ns/op 0 B/op 0 allocs/op BenchmarkAllocOnStack-8 1000000000 0.3148 ns/op 0 B/op 0 allocs/op BenchmarkAllocOnStack-16 1000000000 0.3125 ns/op 0 B/op 0 allocs/op PASS ok command-line-arguments 10.722s ➜ demo vim demo_test.go
➜ demo go test -cpu 1,2,4,8,16 -bench=. demo_test.go goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz BenchmarkAllocOnHeap 1306 916347 ns/op 12001280 B/op 1 allocs/op BenchmarkAllocOnHeap-2 1254 963093 ns/op 12001280 B/op 1 allocs/op BenchmarkAllocOnHeap-4 1263 957514 ns/op 12001282 B/op 1 allocs/op BenchmarkAllocOnHeap-8 1236 980502 ns/op 12001287 B/op 1 allocs/op BenchmarkAllocOnHeap-16 1168 982603 ns/op 12001283 B/op 1 allocs/op BenchmarkAllocOnStack 1218 992638 ns/op 12001280 B/op 1 allocs/op BenchmarkAllocOnStack-2 1161 980680 ns/op 12001280 B/op 1 allocs/op BenchmarkAllocOnStack-4 1218 950208 ns/op 12001286 B/op 1 allocs/op BenchmarkAllocOnStack-8 1164 996705 ns/op 12001290 B/op 1 allocs/op BenchmarkAllocOnStack-16 1119 1018326 ns/op 12001286 B/op 1 allocs/op PASS ok command-line-arguments 14.127s
|
0x01 go逃逸分析
go逃逸分析是在编译阶段分析,它会试图将对象尽可能的保留在stack里面,如果variable分配在heap上面,那么就会发生逃逸.
逃逸机制核心:一个对象处于heap还是stack和它是什么类型,以及是如何被创建出来的没有任何关系,只取决于它是如何被共享的/使用的。
逃逸发生的条件:
- 它的地址可以别别的对象(例如a)通过
&取地址符号获取到
- 对象a本身已经发生了逃逸
当满足上面两个条件的时候就会发生逃逸
1 2 3 4
| 1. construct locations 2. construct edges 3. Anylyze the build graph
|
0x02 go逃逸的几种特殊规则
0x00 大对象
- 显示声明的对象超过10m——发生逃逸
- 通过new/make 声明的对象超过64kb ——发生逃逸


0x01 slice
如果capacity是在编译期间无法确定的值,在这种情况下会发生逃逸


0x02 map
map是一个比较特殊的例子
- map本身是存放在stack里面的
- 任何变量只要被map的key或者value引用到(by reference/by pointer),那么这个变量就会发生逃逸, 不管map本身是不是已经逃逸.


0x03 返回值
如果返回值是指针类型,那么他们就会发生逃逸,如果返回类型是map或者slice,那么他们也会发生逃逸


0x04 输入参数
如果输入形参有泄漏的情况,那么实参数就会发生逃逸
一般input要不return安全的多,尽可能使用input,而不用return


0x05闭包函数
如果内层变量可以被外层通过指针方式引用到,那么也会发生逃逸


0x03 结论
- 尽可能少用指针
- 变数尽可能通过传参方式,少用return方式