当前位置: 首页 > 科技观察

Go语言内存逃逸案例_0

时间:2023-03-18 12:25:18 科技观察

01简介在《Go语言逃逸分析》中,我们学习了内存分配。栈空间分配的开销小,而堆空间分配的开销大。Go语言编译器可以通过逃逸分析判断内存分配到栈空间还是堆空间。但是,在某些情况下,分配在堆栈上的对象会逃逸到堆中。我们可以使用Go工具链来查看对象是否存在内存逃逸。为了提高Go应用程序的性能,我们应该在Go应用程序中避免内存逃逸。在这篇文章中,我们介绍了Go语言中内存逃逸的几个典型案例。02内存转义案例指针转义示例代码:funcmain(){pointerEscape(1,2)}//pointerEscape指针转义funcpointerEscape(a,bint)*int{sum:=a+breturn&sum}输出结果:gobuild--gcflags'-m-m-l'main.go#命令行参数./main.go:9:2:sumescapestoheap:./main.go:9:2:flow:~r0=&sum:./main.go:9:2:from&sum(address-of)at./main.go:10:9./main.go:9:2:fromreturn&sum(return)at./main.go:10:2./main.go:9:2:movedtoheap:sum看上面的代码,我们创建了一个函数pointerEscape,函数内部创建了一个局部变量sum,返回结果是一个指针到变量。通过执行gobuild--gcflags'-m-m-l'main.go的输出,我们发现函数中定义的局部变量sum逃逸到了堆空间,也就是所谓的指针逃逸。函数pointerEscape的局部变量sum本该在函数结束时被回收,但是sum变量的内存地址会在main函数中继续使用,导致变量sum逃逸到堆中。如果想在示例函数的返回结果中避免内存逃逸,可以使用值类型的返回结果,这又带来了另一个问题。如果返回结果是一个比较大的变量,比如返回结果是一个大结构类型的变量,使用值类型会导致内存占用比较大。因此,在实际项目开发中,我们需要根据实际情况合理使用返回结果的类型。动态类型转义示例代码:funcmain(){fmt.Println("helloworld")}输出结果:gorun-gcflags'-m-m-l'main.go#command-line-arguments./main.go:6:14:"helloworld"逃逸到堆:./main.go:6:14:flow:{storagefor...argument}=&{storagefor"helloworld"}:./main.go:6:14:来自./main.go:6:14./main.go:6:14的“helloworld”(溢出):来自./main.go的...参数(切片文字元素):6:13./main.go:6:14:flow:{heap}={storagefor...argument}:./main.go:6:14:from...argument(spill)at./main.go:6:13./main.go:6:14:来自fmt.Println(...argument...)(调用参数)at./main.go:6:13./main.go:6:13:...argumentdoesnotescape./main.go:6:14:"helloworld"escapetoheap阅读上面的代码,我们在main函数中使用fmt.Println()来打印字符串helloworld.通过执行gorun-gcflags'-m-m-l'main.go的输出,我们发现使用fmt.Println()打印的字符串helloworld逃逸到了堆中,也就是所谓的动态类型逃逸.因为fmt.Println()接收到的参数是一个空接口类型,Go编译器无法判断输入参数变量的具体类型,所以这种情况下的变量也会逃逸到堆中。03总结在本文中,我们介绍了两个典型的内存逃逸案例。另外,在以下几种情况下也会发生内存逃逸。向通道发送一个指针或带有指针的值。在切片上存储指针或带有指针的值。切片的底层数组被重新分配(当追加超出其容量时)。有兴趣的读者朋友可以针对上述情况自行编写示例代码来验证是否会发生内存逃逸。