聊聊Golang逃逸分析


逃逸分析的概念,go怎么開啟逃逸分析的log。 以下資料來自互聯網,有錯誤之處,請一定告之。 

什么是逃逸分析

wiki上的定義

在編譯程序優化理論中,逃逸分析是一種確定指針動態范圍的方法——分析在程序的哪些地方可以訪問到指針。它涉及到指針分析和形狀分析。

當一個變量(或對象)在子程序中被分配時,一個指向變量的指針可能逃逸到其它執行線程中,或是返回到調用者子程序。如果使用尾遞歸優化(通常在函數編程語言中是需要的),對象也可以看作逃逸到被調用的子程序中。如果一種語言支持第一類型的延續性在Scheme和Standard ML of New Jersey中同樣如此),部分調用棧也可能發生逃逸。

如果一個子程序分配一個對象並返回一個該對象的指針,該對象可能在程序中被訪問到的地方無法確定——這樣指針就成功“逃逸”了。如果指針存儲在全局變量或者其它數據結構中,因為全局變量是可以在當前子程序之外訪問的,此時指針也發生了逃逸。

逃逸分析確定某個指針可以存儲的所有地方,以及確定能否保證指針的生命周期只在當前進程或線程中。

逃逸分析的用處(為了性能)

  • 最大的好處應該是減少gc的壓力,不逃逸的對象分配在棧上,當函數返回時就回收了資源,不需要gc標記清除。
  • 因為逃逸分析完后可以確定哪些變量可以分配在棧上,棧的分配比堆快,性能好
  • 同步消除,如果你定義的對象的方法上有同步鎖,但在運行時,卻只有一個線程在訪問,此時逃逸分析后的機器碼,會去掉同步鎖運行。

go消除了堆和棧的區別

go在一定程度消除了堆和棧的區別,因為go在編譯的時候進行逃逸分析,來決定一個對象放棧上還是放堆上,不逃逸的對象放棧上,可能逃逸的放堆上。

開啟go編譯時的逃逸分析日志

開啟逃逸分析日志很簡單,只要在編譯的時候加上-gcflags '-m',但是我們為了不讓編譯時自動內連函數,一般會加-l參數,最終為-gcflags '-m -l'

Example:

package main
import ( "fmt" ) func main() { s := "hello" fmt.Println(s) }

go run -gcflags '-m -l' escape.go Output:

# command-line-arguments escape_analysis/main.go:9: s escapes to heap escape_analysis/main.go:9: main ... argument does not escape hello

什么時候逃逸,什么時候不逃逸

Example1:

package main
type S struct{} func main() { var x S y := &x _ = *identity(y) } func identity(z *S) *S { return z }

Output:

# command-line-arguments escape_analysis/main.go:11: leaking param: z to result ~r1 level=0 escape_analysis/main.go:7: main &x does not escape

這里的第一行表示z變量是“流式”,因為identity這個函數僅僅輸入一個變量,又將這個變量作為返回輸出,但identity並沒有引用z,所以這個變量沒有逃逸,而x沒有被引用,且生命周期也在mian里,x沒有逃逸,分配在棧上。

Example2:

package main
type S struct{} func main() { var x S _ = *ref(x) } func ref(z S) *S { return &z }

Output:

# command-line-arguments escape_analysis/main.go:11: &z escapes to heap escape_analysis/main.go:10: moved to heap: z

這里的z是逃逸了,原因很簡單,go都是值傳遞,ref函數copy了x的值,傳給z,返回z的指針,然后在函數外被引用,說明z這個變量在函數內聲明,可能會被函數外的其他程序訪問。所以z逃逸了,分配在堆上

對象里的變量會怎么樣呢?看下面

Example3:

package main
type S struct { M *int } func main() { var i int refStruct(i) } func refStruct(y int) (z S) { z.M = &y return z }

Output:

# command-line-arguments escape_analysis/main.go:13: &y escapes to heap escape_analysis/main.go:12: moved to heap: y

看日志的輸出,這里的y是逃逸了,看來在struct里好像並沒有區別,有可能被函數外的程序訪問就會逃逸

Example4:

package main
type S struct { M *int } func main() { var i int refStruct(&i) } func refStruct(y *int) (z S) { z.M = y return z }

Output:

# command-line-arguments escape_analysis/main.go:12: leaking param: y to result z level=0 escape_analysis/main.go:9: main &i does not escape

這里的y沒有逃逸,分配在棧上,原因和Example1是一樣的。

Example5:

package main
type S struct { M *int } func main() { var x S var i int ref(&i, &x) } func ref(y *int, z *S) { z.M = y }

Output:

# command-line-arguments escape_analysis/main.go:13: leaking param: y escape_analysis/main.go:13: ref z does not escape escape_analysis/main.go:10: &i escapes to heap escape_analysis/main.go:9: moved to heap: i escape_analysis/main.go:10: main &x does not escape

這里的z沒有逃逸,而i卻逃逸了,這是因為go的逃逸分析不知道z和i的關系,逃逸分析不知道參數y是z的一個成員,所以只能把它分配給堆。

參考

Go Escape Analysis Flaws go-escape-analysis


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM