golang內(nèi)存逃逸分析
一、編譯器的逃逸分析
go語言編譯器會自動決定把一個變量放在堆上還是放在棧上,編譯器會做逃逸分析,當(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
果然,在編譯的時候,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ā)生變化,類似上面那個代碼的內(nèi)存地址就會是連續(xù)的。
什么時候編譯器會進(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ī)則
一般我們給一個引用類對象中的引用類成員進(jìn)行賦值,就可能會出現(xiàn)逃逸現(xiàn)象??梢岳斫鉃樵L問一個引用對象實(shí)際上底層就是通過一個指針來間接的訪問了,但是如果再訪問里面的引用成員就會有第二次間接訪問,這樣操作這部分對象的話,就有可能會出現(xiàn)逃逸現(xiàn)象了。
Go 語言的引用類型有:func(函數(shù)類型)、 interface(接口類型)、slice(切片類型)、 map(字典類型)、 channel(管道類型)、 *(指針類型) 等.
案例1
如果一個函數(shù)作為值傳遞給另一個函數(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
對一個[]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)文章
Go語言結(jié)合GORM實(shí)現(xiàn)數(shù)據(jù)庫事務(wù)處理的示例代碼
這篇文章主要為大家詳細(xì)介紹了Go語言結(jié)合GORM實(shí)現(xiàn)數(shù)據(jù)庫事務(wù)處理的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-08-08
在 Golang 中實(shí)現(xiàn)一個簡單的Http中間件過程詳解
本文在go web中簡單的實(shí)現(xiàn)了中間件的機(jī)制,這樣帶來的好處也是顯而易見的,當(dāng)然社區(qū)也有一些成熟的 middleware 組件,包括 Gin 一些Web框架中也包含了 middleware 相關(guān)的功能,具體內(nèi)容詳情跟隨小編一起看看吧2021-07-07
golang接口IP限流,IP黑名單,IP白名單的實(shí)例
這篇文章主要介紹了golang接口IP限流,IP黑名單,IP白名單的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Go語言七篇入門教程二程序結(jié)構(gòu)與數(shù)據(jù)類型
這篇文章主要為大家介紹了Go語言的程序結(jié)構(gòu)與數(shù)據(jù)類型,本篇文章是Go語言七篇入門系列文,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-11-11
淺析在Go語言中如何實(shí)現(xiàn)協(xié)程池
gammazero/workerpool?就是用來實(shí)現(xiàn)協(xié)程池的?Go?包,本文我們將一起來學(xué)習(xí)一下其使用方法,并深入其源碼來探究下如何實(shí)現(xiàn)一個?Go?協(xié)程池,需要的可以了解下2025-06-06

