golang內(nèi)存逃逸分析
一、編譯器的逃逸分析
go語言編譯器會自動決定把一個(gè)變量放在堆上還是放在棧上,編譯器會做逃逸分析,當(dāng)發(fā)現(xiàn)變量的作用域沒有跑出函數(shù)范圍(懸空指針)
,就可以在棧上,否則則必須分配在堆上。
這樣可以釋放程序員關(guān)于內(nèi)存的使用限制,更多的讓程序員關(guān)注于程序功能邏輯本身。
我們看如下代碼:
package main func main() { // 打印返回的指針地址 println(fool()) // 0x1400008e000 } //go:noinline 內(nèi)置的編譯指令,可以強(qiáng)制讓 Go 編譯器不對指定的函數(shù)進(jìn)行內(nèi)聯(lián)優(yōu)化 func fool() *int { var ( a = 1 b = 2 c = 3 d = 4 e = 5 ) println(&a, &b, &c, &d, &e) // 0x14000066f38 0x14000066f30 0x1400008e000 0x14000066f28 0x14000066f20 return &c }
我們能看到**c
**是返回給main 的局部變量,其中它的地址值是 0x1400008e000
很明顯與其他的 a b d e
不是連續(xù)的。
我們用go tool compile
測試一下
~/workspace/test go tool compile -m main.go main.go:3:6: can inline main main.go:14:3: moved to heap: c
果然,在編譯的時(shí)候,c
被編譯器判定為逃逸變量,將c 放在堆中開辟
內(nèi)聯(lián): go編譯器會對一些小函數(shù)進(jìn)行內(nèi)聯(lián)優(yōu)化,以提升性能。內(nèi)聯(lián)優(yōu)化意味著函數(shù)的代碼會在調(diào)用處直接展開,而不是常規(guī)的函數(shù)調(diào)用
。這就導(dǎo)致一些逃逸分析的行為發(fā)生變化,類似上面那個(gè)代碼的內(nèi)存地址就會是連續(xù)的。
什么時(shí)候編譯器會進(jìn)行內(nèi)聯(lián)優(yōu)化?
- 函數(shù)體較?。篏o編譯器更容易將體積較小的函數(shù)進(jìn)行內(nèi)聯(lián)
- 無復(fù)雜控制結(jié)構(gòu):如果函數(shù)內(nèi)沒有復(fù)雜的循環(huán),條件分支等……內(nèi)聯(lián)的可能性更高
- 函數(shù)參數(shù)和返回值簡單:函數(shù)參數(shù)和返回值不過于復(fù)雜也有助于函數(shù)的內(nèi)聯(lián)
二、new的變量內(nèi)存分配在棧還是堆上?
new 出來的變量,內(nèi)存一定是分配在堆上嗎?
還是原來的代碼,我們通過new 分開來看看:
package main func main() { // 打印返回的指針地址 println(fool()) // 0x1400001a0a0 } //go:noinline 內(nèi)置的編譯指令,可以強(qiáng)制讓 Go 編譯器不對指定的函數(shù)進(jìn)行內(nèi)聯(lián)優(yōu)化 func fool() *int { var ( a = new(int) b = new(int) c = new(int) d = new(int) e = new(int) ) println(a, b, c, d, e) // 0x14000098f38 0x14000098f30 0x1400001a0a0 0x14000098f28 0x14000098f20 return c }
很明顯,c
的地址 0x1400001a0a0
依然和其他的不是連續(xù)的內(nèi)存空間,依然具備逃逸行為。所以這里不是分配在堆上的。
結(jié)論:
- new 并不強(qiáng)制堆分配: 使用 new (T)分配的變量不一定分配在堆上,依然依賴于 Go 編譯器的逃逸分析結(jié)果。
- 是否逃逸決定了內(nèi)存分配的位置:如果變量需要在函數(shù)作用域外使用(逃逸),則分配在堆上;如果可以在局部棧中管理,則分配在棧上。
三、逃逸規(guī)則
一般我們給一個(gè)引用類對象中的引用類成員進(jìn)行賦值,就可能會出現(xiàn)逃逸現(xiàn)象。可以理解為訪問一個(gè)引用對象實(shí)際上底層就是通過一個(gè)指針來間接的訪問了,但是如果再訪問里面的引用成員就會有第二次間接訪問,這樣操作這部分對象的話,就有可能會出現(xiàn)逃逸現(xiàn)象了。
Go 語言的引用類型有:func(函數(shù)類型)
、 interface(接口類型)
、slice(切片類型)
、 map(字典類型)
、 channel(管道類型)
、 *(指針類型)
等.
案例1
如果一個(gè)函數(shù)作為值傳遞給另一個(gè)函數(shù),或者被作為閉包使用,生命周期超出其原始作用域,則它會逃逸。
package main func main() { foo()() } //go:noinline func foo() func() { return func() { println("call") } }
通過編譯看看逃逸分析:
~/workspace/test go tool compile -m main.go main.go:9:9: can inline foo.func1 main.go:9:9: func literal escapes to heap
能看到 發(fā)生了逃逸現(xiàn)象
案例2
對一個(gè)[]interface{}
類型嘗試進(jìn)行賦值,必定出現(xiàn)逃逸
package main //go:noinline func main() { var a = []interface{}{"100", "1000"} a[0] = 10 }
逃逸分析:
~/workspace/test go tool compile -m main.go main.go:5:23: []interface {}{...} does not escape main.go:5:24: "100" does not escape main.go:5:31: "1000" does not escape main.go:6:2: 10 escapes to heap
a[0]=10
發(fā)生了逃逸現(xiàn)象
案例3
map[string]interface{}
類型嘗試通過賦值,必定出現(xiàn)逃逸
package main //go:noinline func main() { var a = make(map[string]interface{}) a["hello"] = "world" a["1"] = "1" }
逃逸分析:
~/workspace/test go tool compile -m main.go main.go:5:14: make(map[string]interface {}) does not escape main.go:6:2: "world" escapes to heap main.go:7:2: "1" escapes to heap
a["hello"] = "world"
a["1"] = "1"
分別都發(fā)生了逃逸
案例4
map[interface{}]interface{}
類型嘗試通過賦值,會導(dǎo)致key 和 value 的賦值出現(xiàn)逃逸
package main //go:noinline func main() { var a = make(map[interface{}]interface{}) a["hello"] = "world" }
看看編譯結(jié)果:
~/workspace/test go tool compile -m main.go main.go:5:14: make(map[interface {}]interface {}) does not escape main.go:6:2: "hello" escapes to heap main.go:6:2: "world" escapes to heap
我們能看到,key
和 value
均發(fā)生了逃逸
案例5
map[string][]string
數(shù)據(jù)類型,賦值 []string 會發(fā)生逃逸
package main //go:noinline func main() { var a = make(map[string][]string) a["hello"] = []string{"word1"} }
通過逃逸分析發(fā)現(xiàn):
~/workspace/test go tool compile -m main.go main.go:5:14: make(map[string][]string) does not escape main.go:6:23: []string{...} escapes to heap
[]string{…}
切片發(fā)生了逃逸
案例6
[]*int
數(shù)據(jù)類型,賦值的右側(cè)會發(fā)生逃逸
package main //go:noinline func main() { var a []*int var b = 3 a = append(a, &b) }
逃逸分析:
~/workspace/test go tool compile -m main.go main.go:6:6: moved to heap: b
其中 將 b
追加到 a
切片中, 最終 b
發(fā)生了逃逸
四、結(jié)論
golang 中的變量內(nèi)存分配在堆上還是在棧上,是由編譯器做逃逸分析之后決定的。
到此這篇關(guān)于golang內(nèi)存逃逸分析的文章就介紹到這了,更多相關(guān)golang 內(nèi)存逃逸內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang 如何實(shí)現(xiàn)函數(shù)的任意類型傳參
這篇文章主要介紹了Golang 實(shí)現(xiàn)函數(shù)的任意類型傳參操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go 1.22對net/http包的路由增強(qiáng)功能詳解
Go 1.22 版本對 net/http 包的路由功能進(jìn)行了增強(qiáng),引入了方法匹配(method matching)和通配符(wildcards)兩項(xiàng)新功能,本文將給大家詳細(xì)的介紹一下Go 1.22對net/http包的路由增強(qiáng)功能,需要的朋友可以參考下2024-02-02詳解Go操作supervisor xml rpc接口及注意事項(xiàng)
這篇文章主要介紹了Go操作supervisor xml rpc接口及注意事項(xiàng),管理web,在配置文件中配置相關(guān)信息,通過go-supervisor的處理庫進(jìn)行操作,需要的朋友可以參考下2021-09-09Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解
這篇文章主要介紹了Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10詳解Golang中NewTimer計(jì)時(shí)器的底層實(shí)現(xiàn)原理
本文將主要介紹一下Go語言中的NewTimer,首先展示基于NewTimer創(chuàng)建的定時(shí)器來實(shí)現(xiàn)超時(shí)控制。接著通過一系列問題的跟進(jìn),展示了NewTimer的底層實(shí)現(xiàn)原理,需要的可以參考一下2023-05-05使用Go編譯為可執(zhí)行文件的方法實(shí)現(xiàn)
本文主要介紹了使用Go編譯為可執(zhí)行文件的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04