乐趣区

关于前端:b-boy-vs-b-boy-谁不讲武德golang-逃逸分析入门

背景

最近想要将 protobuf 变量和之前设计的数据对象整合起来,保护在内存中,以缩小内存申请和 GC 的性能损耗。

feature or bug,gogoproto 解码纳闷

因为 gogoproto 在 unmarshal 时不保障输出和输入统一,作为后果的指针变量和输出的字节切片可能不统一(比如说,在 unmarshal slice 时没有 reset 操作)。咱们须要对这个指针变量进行重置,pb 生成文件的 reset 实现办法如下。

func (m Data 远程桌面 ) Reset() { m = Data{}}

在看到 Data{} 时我陷入了纳闷,按我的了解,这一步是须要申请内存的。那么如此一来,咱们在将某个 pb 变量抛入内存时不可避免的还是须要申请内存,这样本次的研发需要如同失去了意义。

我的第一反馈是,这是 gogoproto 的问题,兴许官网 go proto 不是这样的。可是从新生成后发现 reset 办法实现并没有什么区别。只不过官网 go proto 会在 unmarshal 时被动 reset。

那么,难道一开始的方向就错了吗?啊头秃。

柳暗花明又一村

不死心的我开始看各种文档,包含 gogoproto 的各种插件,惋惜并没有找到有用的内容。接着我又开始看官网 proto 文档。。。

这时我发现了一点蛛丝马迹。

在日常应用 protobuf 时,如果不复用旧的变量,咱们个别会

申明指针变量,data := &pb.Data{};

解码,proto.Unmarshal(bytes, data)。

显然,第一步是须要申请内存。而依照 go proto 的源码,unmarshal 时的 reset 操作又会申请一次内存,难道 Google 会容许这种性能损耗?

真的吗,我不信。

逃逸剖析入门

想的太多,不如写个 benchmark 试一下。(小心 microbenchmark 的一些坑)

benchmark

package main

import (

“testing”

)

type boy struct {

name string

age  int

}

var b1 = &boy{}var b2 = &boy{}

func Benchmark_1(b *testing.B) {

for i := 0; i < b.N; i++ {

temp := &boy{}

b1 = temp

}

}

func Benchmark_2(b *testing.B) {

for i := 0; i < b.N; i++ {

temp := &boy{}

b1 = temp

}

}

func Benchmark_3(b *testing.B) {

for i := 0; i < b.N; i++ {

temp := &boy{}

b1 = temp

b2 = temp

}

}

后果如下。

goos: linuxgoarch: amd64pkg: bibleBenchmark_1-4    29142411         42.2 ns/op       32 B/op        1 allocs/opBenchmark_2-4    1000000000          0.711 ns/op        0 B/op        0 allocs/opBenchmark_3-4    28474614         39.5 ns/op       32 B/op        1 allocs/opPASSok   bible 3.258s

后果是高深莫测的,temp := &boy{} 的确没有反复申请内存。

编译报告

到此,只须要一一剖析就好。首先关上编译报告看一下。

go build -gcflags “-m -m”

var b1 = &boy{}//go:noinlinefunc main() {

temp := &boy{}

// &boy literal escapes to heap:

//   flow: temp = &{storage for &boy literal}:

//     from &boy literal (spill) at ./main.go:12:10

//     from temp := &boy literal (assign) at ./main.go:12:7

//   flow: {heap} = temp:

//     from b1 = temp (assign) at ./main.go:13:5

// &boy literal escapes to heap

b1 = temp

}

新创建的 &boy{} 被全局变量援用,于是逃逸到堆上,成为动静变量,无奈被反复利用。

var b1 = &boy{}//go:noinlinefunc main() {

temp := &boy{}

// &boy literal does not escape

b1 = temp

}

b1 = temp 仅仅是赋值操作,新创建的 &boy{} 没有被援用,留在栈上,后续被反复利用。

退出移动版