闽公网安备 35020302035485号
$ go tool compile -m main.go # 或者 $ go build -gcflags='-m -l' main.go• 反汇编
$ go tool compile -S main.go
package main
func main() {
data := []interface{}{100, 200}
data[0] = 100
}
$ go tool compile -m main.go
输出如下main.go:3:6: can inline main
main.go:4:23: []interface {}{...} does not escape
main.go:4:24: 100 does not escape // 未发生逃逸
main.go:4:29: 200 does not escape // 未发生逃逸
main.go:5:2: 100 escapes to heap // 发生逃逸
package performance
import "testing"
const size = 1024
// 堆代码 duidaima.com
func genSeqNumbers() interface{} {
var res [size]int
for i := 0; i < len(res); i++ {
if i <= 1 {
res[i] = 1
continue
}
res[i] = res[i-1] + res[i-2]
}
return res
}
func Benchmark_Compare(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = genSeqNumbers()
}
}
运行测试,并将基准测试结果写入文件:# 运行 100000 次, 统计内存分配 $ go test -run='^$' -bench=. -count=1 -benchtime=100000x -benchmem > slow.txt
package performance
import "testing"
const size = 1024
func genSeqNumbers() [1024]int {
var res [size]int
for i := 0; i < len(res); i++ {
if i <= 1 {
res[i] = 1
continue
}
res[i] = res[i-1] + res[i-2]
}
return res
}
func Benchmark_Compare(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = genSeqNumbers()
}
}
运行测试,并将基准测试结果写入文件:# 运行 100000 次, 统计内存分配 $ go test -run='^$' -bench=. -count=1 -benchtime=100000x -benchmem > fast.txt使用 benchstat 比较差异
$ benchstat -alpha=100 fast.txt slow.txt输出如下
name old time/op new time/op delta _Compare-8 1.44µs ± 0% 3.22µs ± 0% +123.40% (p=1.000 n=1+1) name old alloc/op new alloc/op delta _Compare-8 0.00B 8192.00B ± 0% +Inf% (p=1.000 n=1+1) name old allocs/op new allocs/op delta _Compare-8 0.00 1.00 ± 0% +Inf% (p=1.000 n=1+1)从输出的结果中可以看到,通过使用确定的数据类型,运行时间提升了 1 倍多, 内存分配量和内存分配次数降为 0。高性能 Tips: 在 hot path 上要尽可能返回具体的数据类型。
package main
func main() {
weekdays := 7
data := make(map[string][]int)
data["weeks"] = make([]int, weekdays)
data["weeks"] = append(data["weeks"], []int{0, 1, 2, 3, 4, 5, 6}...)
}
$ go tool compile -m main.go
输出如下 (发生逃逸)main.go:3:6: can inline main
main.go:5:14: make(map[string][]int) does not escape
main.go:6:22: make([]int, weekdays) escapes to heap
main.go:7:45: []int{...} does not escape
避免逃逸方案package main
func main() {
data := make(map[string][7]int)
data["weeks"] = [...]int{0, 1, 2, 3, 4, 5, 6}
}
$ go tool compile -m main.go
输出如下 (没有发生逃逸)main.go:3:6: can inline main main.go:4:14: make(map[string][7]int) does not escape指针
package main
func main() {
n := 10
data := make([]*int, 1)
data[0] = &n
}
$ go tool compile -m main.go
输出如下 (发生逃逸)main.go:3:6: can inline main main.go:4:2: moved to heap: n main.go:5:14: make([]*int, 1) does not escapeinterface{}
package main
func main() {
data := make(map[interface{}]interface{})
data[100] = 200
}
$ go tool compile -m main.go
输出如下 (发生逃逸)main.go:3:6: can inline main
main.go:4:14: make(map[interface {}]interface {}) does not escape
main.go:5:2: 100 escapes to heap
main.go:5:2: 200 escapes to heap
package main
import "math/rand"
func foo(argVal int) *int {
var fooVal1 = 11
var fooVal2 = 12
var fooVal3 = 13
var fooVal4 = 14
var fooVal5 = 15
// 堆代码 duidaima.com
// 循环是防止编译器将 foo 函数优化为 inline
// 如果不用随机数指定循环次数,也可能被编译器优化为 inline
// 如果是内联函数,main 调用 foo 将是原地展开
// 那么 fooVal1 ... fooVal5 相当于 main 作用域的变量
// 即使 fooVal3 发生逃逸,地址与其他几个变量也是连续的
n := rand.Intn(5) + 1
for i := 0; i < n; i++ {
println(&argVal, &fooVal1, &fooVal2, &fooVal3, &fooVal4, &fooVal5)
}
return &fooVal3
}
func main() {
mainVal := foo(1)
println(*mainVal, mainVal)
}
运行代码$ go run main.go输出如下 (发生逃逸)
0xc000114f58 0xc000114f38 0xc000114f30 0xc000120000 0xc000114f28 0xc000114f20 0xc000114f58 0xc000114f38 0xc000114f30 0xc000120000 0xc000114f28 0xc000114f20 13 0xc000120000通过输出的结果可以看到,变量 fooVal3 的地址明显与其他变量地址不是连续的。
结果分析
$ go tool compile -m main.go输出如下 (发生逃逸)
main.go:16:16: inlining call to rand.Intn main.go:24:6: can inline main main.go:8:6: **moved to heap: fooVal3** # 查看逃逸分析详情 $ go build -gcflags='-m -l' main.go # 输出如下 (发生逃逸) ./main.go:5:6: cannot inline foo: function too complex: cost 124 exceeds budget 80 ... ./main.go:8:6: fooVal3 escapes to heap: ... ./main.go:8:6: **moved to heap: fooVal3** # 或者 $ go tool compile -S main.go | grep "runtime.newobject" # 输出如下 0x0036 00054 (**main.go:8**) CALL runtime.newobject(SB) rel 55+4 t=7 runtime.newobject+0 # main.go 第 8 行正好是 var fooVal3 = 13通道
package main
func main() {
ch := make(chan string)
s := "hello world"
go func() {
ch <- s
}()
<-ch
}
$ go tool compile -m main.go
# 输出如下 (发生逃逸)
main.go:19:5: can inline main.func1
main.go:19:5: func literal escapes to heap
闭包package main
func inc() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
in := inc()
println(in()) // 1
println(in()) // 2
}
$ go tool compile -m main.go
# 输出如下 (发生逃逸)
main.go:3:6: can inline inc
main.go:5:9: can inline inc.func1
main.go:12:11: inlining call to inc
main.go:5:9: can inline main.func1
main.go:13:12: inlining call to main.func1
main.go:14:12: inlining call to main.func1
main.go:4:2: **moved to heap: n**
main.go:5:9: func literal escapes to heap
main.go:12:11: func literal does not escape
inc() 函数返回一个闭包函数,闭包函数内部访问了外部变量 n, 形成了引用关系,那么外部变量 n 将会一直存在,直到 inc() 函数被销毁, 所以最终外部变量 n 被分配到了 堆 上。package main
import "math/rand"
func generate8192() {
nums := make([]int, 8192) // = 64KB
for i := 0; i < 8192; i++ {
nums[i] = rand.Int()
}
}
func generate8193() {
nums := make([]int, 8193) // < 64KB
for i := 0; i < 8193; i++ {
nums[i] = rand.Int()
}
}
func generate(n int) {
nums := make([]int, n) // 不确定大小
for i := 0; i < n; i++ {
nums[i] = rand.Int()
}
}
func main() {
generate8192()
generate8193()
generate(1)
}
$ go tool compile -m main.go
# 输出如下 (发生逃逸)
main.go:6:14: make([]int, 8192) does not escape
main.go:13:14: make([]int, 8193) escapes to heap
main.go:20:14: make([]int, n) escapes to heap
从输出的结果中可以看到,make([]int, 8192) 没有发生逃逸,make([]int, 8193) 和 make([]int, n) 逃逸到 堆 上, 说明当切片占用内存超过一定大小,或无法确定当前切片长度时,对象将分配到 堆 上。