golang內存逃逸分析
一、編譯器的逃逸分析
go語言編譯器會自動決定把一個變量放在堆上還是放在棧上,編譯器會做逃逸分析,當發(fā)現(xiàn)變量的作用域沒有跑出函數(shù)范圍(懸空指針)
,就可以在棧上,否則則必須分配在堆上。
這樣可以釋放程序員關于內存的使用限制,更多的讓程序員關注于程序功能邏輯本身。
我們看如下代碼:
package main func main() { // 打印返回的指針地址 println(fool()) // 0x1400008e000 } //go:noinline 內置的編譯指令,可以強制讓 Go 編譯器不對指定的函數(shù)進行內聯(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 放在堆中開辟
內聯(lián): go編譯器會對一些小函數(shù)進行內聯(lián)優(yōu)化,以提升性能。內聯(lián)優(yōu)化意味著函數(shù)的代碼會在調用處直接展開,而不是常規(guī)的函數(shù)調用
。這就導致一些逃逸分析的行為發(fā)生變化,類似上面那個代碼的內存地址就會是連續(xù)的。
什么時候編譯器會進行內聯(lián)優(yōu)化?
- 函數(shù)體較?。篏o編譯器更容易將體積較小的函數(shù)進行內聯(lián)
- 無復雜控制結構:如果函數(shù)內沒有復雜的循環(huán),條件分支等……內聯(lián)的可能性更高
- 函數(shù)參數(shù)和返回值簡單:函數(shù)參數(shù)和返回值不過于復雜也有助于函數(shù)的內聯(lián)
二、new的變量內存分配在棧還是堆上?
new 出來的變量,內存一定是分配在堆上嗎?
還是原來的代碼,我們通過new 分開來看看:
package main func main() { // 打印返回的指針地址 println(fool()) // 0x1400001a0a0 } //go:noinline 內置的編譯指令,可以強制讓 Go 編譯器不對指定的函數(shù)進行內聯(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ù)的內存空間,依然具備逃逸行為。所以這里不是分配在堆上的。
結論:
- new 并不強制堆分配: 使用 new (T)分配的變量不一定分配在堆上,依然依賴于 Go 編譯器的逃逸分析結果。
- 是否逃逸決定了內存分配的位置:如果變量需要在函數(shù)作用域外使用(逃逸),則分配在堆上;如果可以在局部棧中管理,則分配在棧上。
三、逃逸規(guī)則
一般我們給一個引用類對象中的引用類成員進行賦值,就可能會出現(xiàn)逃逸現(xiàn)象??梢岳斫鉃樵L問一個引用對象實際上底層就是通過一個指針來間接的訪問了,但是如果再訪問里面的引用成員就會有第二次間接訪問,這樣操作這部分對象的話,就有可能會出現(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{}
類型嘗試進行賦值,必定出現(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{}
類型嘗試通過賦值,會導致key 和 value 的賦值出現(xiàn)逃逸
package main //go:noinline func main() { var a = make(map[interface{}]interface{}) a["hello"] = "world" }
看看編譯結果:
~/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ù)類型,賦值的右側會發(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ā)生了逃逸
四、結論
golang 中的變量內存分配在堆上還是在棧上,是由編譯器做逃逸分析之后決定的。
到此這篇關于golang內存逃逸分析的文章就介紹到這了,更多相關golang 內存逃逸內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Golang 如何實現(xiàn)函數(shù)的任意類型傳參
這篇文章主要介紹了Golang 實現(xiàn)函數(shù)的任意類型傳參操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04詳解Go操作supervisor xml rpc接口及注意事項
這篇文章主要介紹了Go操作supervisor xml rpc接口及注意事項,管理web,在配置文件中配置相關信息,通過go-supervisor的處理庫進行操作,需要的朋友可以參考下2021-09-09Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解
這篇文章主要介紹了Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10詳解Golang中NewTimer計時器的底層實現(xiàn)原理
本文將主要介紹一下Go語言中的NewTimer,首先展示基于NewTimer創(chuàng)建的定時器來實現(xiàn)超時控制。接著通過一系列問題的跟進,展示了NewTimer的底層實現(xiàn)原理,需要的可以參考一下2023-05-05