一文弄清楚Golang内存逃逸
1. 为什么要了解内存逃逸
c/c++的programmer对于堆内存、栈内存一定非常熟悉,以为内存管理完全由使用者自己管理。Go语言的内存管理完全由Go的runtime接管,那么是不程序员就完全不用care变量是如何分配的呢?减少了gc压力。如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销,甚至会导致STW(stop the world)。提高分配效率。堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。但当我们的服务发现性能瓶颈,要如何去定位瓶颈,让我们的程序运行的更快,就非常有必要了解Go的内存分配。2. 什么是内存逃逸
Go语言中局部的非指针变量通常是不受GC管理的,这种Go变量的内存分配称为"栈分配",处于goroutine自己的栈中。由于Go编译器无法确定其生命周期,因此无法以这种方式分配内存的Go变量会逃逸到堆上,被称为 内存逃逸 。3. 哪些情况下会发生内存逃逸
先来说一下通过go编译器查看内存逃逸方式go build -gcflags=-m xxx.go局部变量被返回造成逃逸package main type User struct { Namestring } func foo(s string) *User { u := new(User) u.Name= s return u // 方法内局部变量返回,逃逸 } func main() { user := foo("hui") user.Name= "dev" } //# command-line-arguments //./escape.go:7:6: can inline foo //./escape.go:13:6: can inline main //./escape.go:14:13: inlining call to foo //./escape.go:7:10: leaking param: s //./escape.go:8:10: new(User) escapes to heap //./escape.go:14:13: new(User) does not escape interface{}动态类型 逃逸package main import "fmt" func main() { name := "devhui" fmt.Println(name) } //# command-line-arguments //./escape_02.go:7:13: inlining call to fmt.Println //./escape_02.go:7:13: name escapes to heap //./escape_02.go:7:13: []interface {}{...} does not escape //:1: leaking param content: .this
很多函数的参数为interface{} 空接口类型,这些都会造成逃逸。比如func Printf(format string, a ...interface{}) (n int, err error) func Sprintf(format string, a ...interface{}) string func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Print(a ...interface{}) (n int, err error) func Println(a ...interface{}) (n int, err error) 复制代码
编译期间很难确定其参数的具体类型,也能产生逃逸func main() { fmt.Println("hello 逃逸") } /* 逃逸日志分析 ./main.go:5:6: can inline main ./main.go:6:13: inlining call to fmt.Println ./main.go:6:14: "hello 逃逸" escapes to heap ./main.go:6:13: []interface {} literal does not escape */ 栈空间不足逃逸package main func main() { s := make([]int, 1000, 1000) for index, _ := range s { s[index] = index s1 := make([]int, 10000, 10000) for index, _ := range s1 { s1[index] = index } }
逃逸分析:./escape_03.go:4:11: make([]int, 1000, 1000) does not escape ./escape_03.go:9:12: make([]int, 10000, 10000) escapes to heap
s足够在栈空间分配没有逃逸;s1空间不够在栈内分配发生了逃逸。变量大小不确定(如 slice 长度或容量不定)package main func main() { s := make([]int, 0, 1000) for index, _ := range s { s[index] = index } num := 1000 s1 := make([]int, 0, num) for index, _ := range s1 { s1[index] = index } }
逃逸分析:./escape_05.go:4:11: make([]int, 0, 1000) does not escape ./escape_05.go:10:12: make([]int, 0, num) escapes to heap
s分配时cap是一个常量没有发生逃逸,s1的cap是一个变量发生了逃逸。闭包func Increase() func() int { n := 0 return func() int { n++ return n } } func main() { in := Increase() fmt.Println(in()) // 1 fmt.Println(in()) // 2 } //./escape_04.go:6:2: moved to heap: n //./escape_04.go:7:9: func literal escapes to heap //./escape_04.go:7:9: func literal does not escape //./escape_04.go:15:16: int(~R0) escapes to heap //./escape_04.go:15:13: []interface {}{...} does not escape //./escape_04.go:16:16: int(~R0) escapes to heap //./escape_04.go:16:13: []interface {}{...} does not escape //:1: leaking param content: .this 4. 如何减少逃逸局部切片尽可能确定长度或容量benchmark testimport "testing" // sliceEscape 发生逃逸,在堆上申请切片 func sliceEscape() { number := 10 s1 := make([]int, 0, number) for i := 0; i < number; i++ { s1 = append(s1, i) } } // sliceNoEscape 不逃逸,限制在栈上 func sliceNoEscape() { s1 := make([]int, 0, 10) for i := 0; i < 10; i++ { s1 = append(s1, i) } } func BenchmarkSliceEscape(b *testing.B) { for i := 0; i < b.N; i++ { sliceEscape() } } func BenchmarkSliceNoEscape(b *testing.B) { for i := 0; i < b.N; i++ { sliceNoEscape() } } 测试结果:BenchmarkSliceEscape BenchmarkSliceEscape-10 53271513 22.09 ns/op BenchmarkSliceNoEscape BenchmarkSliceNoEscape-10 187033111 6.458 ns/op 合理选择返回值、返回指针返回指针可以避免值的拷贝,但是会导致内存分配逃逸到堆中,增加GC的负担。一般情况下,对于需要修改原对象,或占用内存比较大的对象,返回指针。对于只读或占用内存较小的对象,返回值能够获得更好的性能。benchmark testpackage escape_bench_02 import "testing" type St struct { arr [100]int } func retValue() St { var st St return st } func retPtr() *St { var st St return &st } func BenchmarkRetValue(b *testing.B) { for i := 0; i < b.N; i++ { _ = retValue() } } func BenchmarkRetPtr(b *testing.B) { for i := 0; i < b.N; i++ { _ = retPtr() } } 测试结果BenchmarkRetValue-10 34714424 34.45 ns/op 0 B/op 0 allocs/op BenchmarkRetPtr-10 8038676 145.3 ns/op 896 B/op 1 allocs/op
可以看到返回值更快且没有发生堆内存的分配。小的拷贝好过引用benchmark testpackage escape_bench_03 import "testing" const capacity = 1024 func arrayFibonacci() [capacity]int { var d [capacity]int for i := 0; i < len(d); i++ { if i <= 1 { d[i] = 1 continue } d[i] = d[i-1] + d[i-2] } return d } func sliceFibonacci() []int { d := make([]int, capacity) for i := 0; i < len(d); i++ { if i <= 1 { d[i] = 1 continue } d[i] = d[i-1] + d[i-2] } return d } func BenchmarkArray(b *testing.B) { for i := 0; i < b.N; i++ { _ = arrayFibonacci() } } func BenchmarkSlice(b *testing.B) { for i := 0; i < b.N; i++ { _ = sliceFibonacci() } } 测试结果:BenchmarkArray-10 346110 2986 ns/op 0 B/op 0 allocs/op BenchmarkSlice-10 389745 2849 ns/op 8192 B/op 1 allocs/op
那么多大的变量才算是小变量呢? 对 Go 编译器而言,超过一定大小的局部变量将逃逸到堆上,不同 Go 版本的大小限制可能不一样。一般是 < 64KB,局部变量将不会逃逸到堆上。返回值使用确定的类型benchmark testpackage escape_bench_04 import "testing" const capacity = 1024 func returnArray() [capacity]int { var arr [capacity]int for i := 0; i < len(arr); i++ { arr[i] = 1000 } return arr } func returnInterface() interface{} { var arr [capacity]int for i := 0; i < len(arr); i++ { arr[i] = 1000 } return arr } func BenchmarkReturnArray(b *testing.B) { for i := 0; i < b.N; i++ { _ = returnArray() } } func BenchmarkReturnInterface(b *testing.B) { for i := 0; i < b.N; i++ { _ = returnInterface() } } 测试结果
OPPOFindN2Flip值得入手吗?数码博主实测之后说出感受2021年,OPPO推出了第一款折叠屏手机OPPOFindN,这款折叠产品颠覆了当时主流的折叠屏定义,为折叠屏带来了黄金比例设计。一开始网络上有不理解的声音,但是随后在京东的连续多
最新Nature子刊,液滴微流控相关!想象一下,如果我们身体里的细胞无法全部打乱成均匀分布会怎样?实际上,在那种情况下,基本已经只能算是一个巨型的多细胞团了,因为在生物体内,只有各种不同的细胞能够被精确的分选定位,才能
酶制剂巨头诺维信和科汉森合并推动生物解决方案发展快报摘要WrapUp大企业动向BigPlayer普利制药通过合成生物学技术开发公斤级红景天苷酶制剂巨头诺维信和科汉森合并推动生物解决方案发展日本帝人子公司开发新型可降解PLA树脂材
美国2022通胀高烧持续(华府观察)美国2022通胀高烧持续中新社华盛顿12月16日电题美国2022通胀高烧持续中新社记者王帆美国今年遭遇了40年来最严重的通货膨胀。广受关注的通胀指标消费者价格指数(CP
超级投资人专访罗杰斯中国必将成为最成功国家,明年中国经济能更快恢复3529视频加载中编者按2022年,全球经济增长放缓,通胀高位运行,地缘政治冲突持续,美联储持续加息,全球经济复苏面临重重考验。超级投资人们如何挖掘中国机遇,如何布局全球市场?又如
刚刚发布!晋江,全国第四!2022年12月16日,全国县域经济研究咨询专业智库社会组织中郡研究所完成并发布了第二十二届县域经济与县域发展监测评价报告,揭晓第二十二届全国县域经济基本竞争力百强县全国县域现代化
碳金融市场概述(壹)碳金融市场形成的背景第五章碳金融市场5。1碳金融市场概述5。1。1碳金融市场形成的背景5。1。1碳金融市场形成的背景思维导图1。气候变化受到关注伴随人类社会的进步,全球经济发展迅猛,对环境和资源的压力
强品种,索通发展预焙阳极行业龙头,升级循环经济的领军者(报告出品方分析师东吴证券刘博唐亚辉)1。预焙阳极行业市占率第一,近年来公司出口量占比全行业出口接近40公司成立于2003年,是一家专业从事铝用预焙阳极研发生产和销售的高新技术企业
六措并举鞍本重组整合见行见效日前,鞍钢集团举办了鞍本整合重组一周年线上新闻发布会,宣布鞍本重组成功,构建了中国钢铁产业新格局,成为国企改革三年行动标志性案例。与普通的企业重组不尽相同,鞍本重组是多维度系统性的
连续亏损两度陷入召回,软包电池龙头的国内困境丨智氪作者范亮编辑黄绎达封面来源视觉中国2009年,就在宁德时代成立前夕,华人科学家王瑀被我国人才计划引入国内,随王瑀一同回国的,还有其2002年创立的软包动力电池企业孚能科技。软包电池
随着生活节奏的加快,这些养生知识你必须要了解大家好!因为咱们现在生活节奏加快的原因,很多人和小编一样,都明显的感觉生活压力很大,因此,身体也陆续出现了亚健康的状态。本期就让小编带你来了解一下健康养生方面的知识,有需要的朋友请