1。为什么要了解内存逃逸 cc的programmer对于堆内存、栈内存一定非常熟悉,以为内存管理完全由使用者自己管理。Go语言的内存管理完全由Go的runtime接管,那么是不程序员就完全不用care变量是如何分配的呢?减少了gc压力。如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销,甚至会导致STW(stoptheworld)。提高分配效率。堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。但当我们的服务发现性能瓶颈,要如何去定位瓶颈,让我们的程序运行的更快,就非常有必要了解Go的内存分配。2。什么是内存逃逸 Go语言中局部的非指针变量通常是不受GC管理的,这种Go变量的内存分配称为栈分配,处于goroutine自己的栈中。由于Go编译器无法确定其生命周期,因此无法以这种方式分配内存的Go变量会逃逸到堆上,被称为内存逃逸。3。哪些情况下会发生内存逃逸 先来说一下通过go编译器查看内存逃逸方式gobuildgcflagsmxxx。go局部变量被返回造成逃逸packagemaintypeUserstruct{Namestring}funcfoo(sstring)User{u:new(User)u。Namesreturnu方法内局部变量返回,逃逸}funcmain(){user:foo(hui)user。Namedev}commandlinearguments。escape。go:7:6:caninlinefoo。escape。go:13:6:caninlinemain。escape。go:14:13:inliningcalltofoo。escape。go:7:10:leakingparam:s。escape。go:8:10:new(User)escapestoheap。escape。go:14:13:new(User)doesnotescapeinterface{}动态类型逃逸packagemainimportfmtfuncmain(){name:devhuifmt。Println(name)}commandlinearguments。escape02。go:7:13:inliningcalltofmt。Println。escape02。go:7:13:nameescapestoheap。escape02。go:7:13:〔〕interface{}{。。。}doesnotescape:1:leakingparamcontent:。this 很多函数的参数为interface{}空接口类型,这些都会造成逃逸。比如funcPrintf(formatstring,a。。。interface{})(nint,errerror)funcSprintf(formatstring,a。。。interface{})stringfuncFprint(wio。Writer,a。。。interface{})(nint,errerror)funcPrint(a。。。interface{})(nint,errerror)funcPrintln(a。。。interface{})(nint,errerror)复制代码 编译期间很难确定其参数的具体类型,也能产生逃逸funcmain(){fmt。Println(hello逃逸)}逃逸日志分析。main。go:5:6:caninlinemain。main。go:6:13:inliningcalltofmt。Println。main。go:6:14:hello逃逸escapestoheap。main。go:6:13:〔〕interface{}literaldoesnotescape栈空间不足逃逸packagemainfuncmain(){s:make(〔〕int,1000,1000)forindex,:ranges{s〔index〕indexs1:make(〔〕int,10000,10000)forindex,:ranges1{s1〔index〕index}} 逃逸分析:。escape03。go:4:11:make(〔〕int,1000,1000)doesnotescape。escape03。go:9:12:make(〔〕int,10000,10000)escapestoheap s足够在栈空间分配没有逃逸;s1空间不够在栈内分配发生了逃逸。变量大小不确定(如slice长度或容量不定)packagemainfuncmain(){s:make(〔〕int,0,1000)forindex,:ranges{s〔index〕index}num:1000s1:make(〔〕int,0,num)forindex,:ranges1{s1〔index〕index}} 逃逸分析:。escape05。go:4:11:make(〔〕int,0,1000)doesnotescape。escape05。go:10:12:make(〔〕int,0,num)escapestoheap s分配时cap是一个常量没有发生逃逸,s1的cap是一个变量发生了逃逸。闭包funcIncrease()func()int{n:0returnfunc()int{nreturnn}}funcmain(){in:Increase()fmt。Println(in())1fmt。Println(in())2}。escape04。go:6:2:movedtoheap:n。escape04。go:7:9:funcliteralescapestoheap。escape04。go:7:9:funcliteraldoesnotescape。escape04。go:15:16:int(R0)escapestoheap。escape04。go:15:13:〔〕interface{}{。。。}doesnotescape。escape04。go:16:16:int(R0)escapestoheap。escape04。go:16:13:〔〕interface{}{。。。}doesnotescape:1:leakingparamcontent:。this4。如何减少逃逸局部切片尽可能确定长度或容量benchmarktestimporttestingsliceEscape发生逃逸,在堆上申请切片funcsliceEscape(){number:10s1:make(〔〕int,0,number)fori:0;i{s1append(s1,i)}}sliceNoEscape不逃逸,限制在栈上funcsliceNoEscape(){s1:make(〔〕int,0,10)fori:0;i10;i{s1append(s1,i)}}funcBenchmarkSliceEscape(btesting。B){fori:0;ib。N;i{sliceEscape()}}funcBenchmarkSliceNoEscape(btesting。B){fori:0;ib。N;i{sliceNoEscape()}}测试结果:BenchmarkSliceEscapeBenchmarkSliceEscape105327151322。09nsopBenchmarkSliceNoEscapeBenchmarkSliceNoEscape101870331116。458nsop合理选择返回值、返回指针返回指针可以避免值的拷贝,但是会导致内存分配逃逸到堆中,增加GC的负担。一般情况下,对于需要修改原对象,或占用内存比较大的对象,返回指针。对于只读或占用内存较小的对象,返回值能够获得更好的性能。benchmarktestpackageescapebench02importtestingtypeStstruct{arr〔100〕int}funcretValue()St{varstStreturnst}funcretPtr()St{varstStreturnst}funcBenchmarkRetValue(btesting。B){fori:0;ib。N;i{retValue()}}funcBenchmarkRetPtr(btesting。B){fori:0;ib。N;i{retPtr()}}测试结果BenchmarkRetValue103471442434。45nsop0Bop0allocsopBenchmarkRetPtr108038676145。3nsop896Bop1allocsop 可以看到返回值更快且没有发生堆内存的分配。小的拷贝好过引用benchmarktestpackageescapebench03importtestingconstcapacity1024funcarrayFibonacci()〔capacity〕int{vard〔capacity〕intfori:0;ilen(d);i{ifi1{d〔i〕1continue}d〔i〕d〔i1〕d〔i2〕}returnd}funcsliceFibonacci()〔〕int{d:make(〔〕int,capacity)fori:0;ilen(d);i{ifi1{d〔i〕1continue}d〔i〕d〔i1〕d〔i2〕}returnd}funcBenchmarkArray(btesting。B){fori:0;ib。N;i{arrayFibonacci()}}funcBenchmarkSlice(btesting。B){fori:0;ib。N;i{sliceFibonacci()}}测试结果:BenchmarkArray103461102986nsop0Bop0allocsopBenchmarkSlice103897452849nsop8192Bop1allocsop 那么多大的变量才算是小变量呢?对Go编译器而言,超过一定大小的局部变量将逃逸到堆上,不同Go版本的大小限制可能不一样。一般是64KB,局部变量将不会逃逸到堆上。返回值使用确定的类型benchmarktestpackageescapebench04importtestingconstcapacity1024funcreturnArray()〔capacity〕int{vararr〔capacity〕intfori:0;ilen(arr);i{arr〔i〕1000}returnarr}funcreturnInterface()interface{}{vararr〔capacity〕intfori:0;ilen(arr);i{arr〔i〕1000}returnarr}funcBenchmarkReturnArray(btesting。B){fori:0;ib。N;i{returnArray()}}funcBenchmarkReturnInterface(btesting。B){fori:0;ib。N;i{returnInterface()}}测试结果