本篇内容介绍了“如何理解golang逃逸分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联专注于企业全网营销推广、网站重做改版、梅列网站定制设计、自适应品牌网站建设、HTML5建站、商城网站定制开发、集团公司官网建设、外贸网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为梅列等各大城市提供网站开发制作服务。
背景
最近想要将 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: linux goarch: amd64 pkg: bible Benchmark_1-4 29142411 42.2 ns/op 32 B/op 1 allocs/op Benchmark_2-4 1000000000 0.711 ns/op 0 B/op 0 allocs/op Benchmark_3-4 28474614 39.5 ns/op 32 B/op 1 allocs/op PASS ok bible 3.258s
结果是一目了然的,temp := &boy{} 确实没有重复申请内存。
编译报告
到此,只需要逐个分析就好。首先打开编译报告看一下。
go build -gcflags "-m -m"
var b1 = &boy{}
//go:noinline
func 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:noinline
func main() {
	temp := &boy{}
	// &boy literal does not escape
	*b1 = *temp
}*b1 = *temp 仅仅是赋值操作,新创建的 &boy{} 没有被引用,留在栈上,后续被重复利用。
汇编分析

“如何理解golang逃逸分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
标题名称:如何理解golang逃逸分析
标题网址:http://www.scyingshan.cn/article/jpigcd.html

 建站
建站
 咨询
咨询 售后
售后
 建站咨询
建站咨询 
 