中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

golang逃逸的示例分析

發布時間:2021-07-01 15:02:26 來源:億速云 閱讀:199 作者:小新 欄目:編程語言

這篇文章主要介紹了golang逃逸的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。


垃圾回收是Go的一個很方便的特性--其自動的內存管理使代碼更整潔,同時減少內存泄漏的可能性。但是,由于垃圾回收需要周期性的停止程序從而去收集不用的對象,不可避免的會增加額外開銷。Go編譯器是智能的,它會自動決定一個變量是應該分配在堆上從而在將來便于回收,還是直接分配到函數的棧空間。對于分配到棧上的變量,其與分配到堆上的變量不同之處在于:隨著函數的返回,棧空間會被銷毀,從而棧上的變量被直接銷毀,不需要額外的垃圾回收開銷。

Go的逃逸分析相對于Java虛擬機的HotSpot來說更為基礎。基本規則就是,如果一個變量的引用從聲明它的函數中返出去了,則發生“逃逸”,因為它有可能在函數外被別的內容使用,所以必須分配到堆上。如下幾種情況會比較復雜:

  • 函數調用其他函數

  • 引用作為結構體的成員變量

  • 切片和映射

  • Cgo指向變量的指針

為了實現逃逸分析,Go會在編譯階段構造函數調用關系圖,同時跟蹤入參和返回值的流程。一個函數如果只是引用一個參數,但這個引用并沒有返出函數的話,這個變量也不會逃逸。如果一個函數返回了一個引用,但是這個引用被棧中的其他函數解除或者沒有返回此引用,則也不會逃逸。為了論證幾個例子,可以在編譯時加-gcflags '-m'參數,這個參數會打印逃逸分析的詳細信息:

package main

type S struct {}

func main() {
    var x S
    _ = identity(x)
}

func identity(x S) S {
    return x
}

你可以執行go run -gcflags '-m -l'(注:原文中略了go代碼文件名)來編譯這個代碼,-l參數是防止函數identity被內聯(換個時間再討論內聯這個話題)。你將會看到沒有任何輸出!Go使用值傳遞,所以main函數中的x這個變量總是會被拷貝到函數identity的棧空間。通常情況下沒有使用引用的代碼都是通過棧空間來分配內存。所以不涉及逃逸分析。下面試下困難一點的:

package main

type S struct {}

func main() {
    var x S
    y := &x
    _ = *identity(y)
}

func identity(z *S) *S {
    return z
}

其對應的輸出是:

./escape.go:11: leaking param: z to result ~r1
./escape.go:7: main &x does not escape

第一行顯示了變量z的“流經”:入參直接作為返回值返回了。但是函數identity沒有取走z這個引用,所以沒有發生變量逃逸。在main函數返回后沒有任何對x的引用存在,所以x這個變量可以在main函數的棧空間進行內存分配。
第三次實驗:

package main

type S struct {}

func main() {
  var x S
  _ = *ref(x)
}

func ref(z S) *S {
  return &z
}

其輸出為:

./escape.go:10: moved to heap: z
./escape.go:11: &z escapes to heap

現在有了逃逸發生。記住Go是值傳遞的,所以z是對變量x的一個拷貝。函數ref返回一個對z的引用,所以z不能在棧中分配,否則當函數ref返回時,引用會指向何處呢?于是它逃逸到了堆中。其實執行完ref返回到main函數中后,main函數丟棄了這個引用而不是解除引用,但是Go的逃逸分析還不夠機智去識別這種情況。
值得注意的是,在這種情況下,如果我們不停止引用,編譯器將內聯ref
如果結構體成員定義的是引用又會怎樣呢?

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
}

其輸出為:

./escape.go:12: moved to heap: y
./escape.go:13: &y escapes to heap

在這種情況下,盡管引用是結構體的成員,但Go仍然會跟蹤引用的流向。由于函數refStruct接受引用并將其返回,因此y必須逃逸。對比如下這個例子:

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
}

其輸出為:

./escape.go:12: leaking param: y to result z
./escape.go:9: main &i does not escape

盡管在main函數中對i變量做了引用操作,并傳遞到了函數refStruct中,但是這個引用的范圍沒有超出其聲明它的棧空間。這和之前的那個程序語義上有細微的差別,這個會更高效:在上一個程序中,變量i必須分配在main函數的棧中,然后作為參數拷貝到函數refStruct中,并將拷貝的這一份分配在堆上。而在這個例子中,i僅被分配一次,然后將引用到處傳遞。

再來看一個有點彎彎繞的例子:

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
}

其輸出為:

./escape.go:13: leaking param: y
./escape.go:13: ref z does not escape
./escape.go:9: moved to heap: i
./escape.go:10: &i escapes to heap
./escape.go:10: main &x does not escape

問題在于,y被賦值給了一個入參結構體的成員。Go并不能追溯這種關系(go只能追溯輸入直接流向輸出),所以逃逸分析失敗了,所以變量只能分配到堆上。由于Go的逃逸分析的局限性,許多變量會被分配到堆上,請參考此鏈接,這里面記錄了許多案例(從Go1.5開始)。

最后,來看下映射和切片是怎樣的呢?請記住,切片和映射實際上只是具有指向堆內存的指針的Go結構:slice結構是暴露在reflect包中(SliceHeader
)。map結構就更隱蔽了:存在于hmap。如果這些結構體不逃逸,將會被分配到棧上,但是其底層的數組或者哈希桶中的實際數據會被分配到堆上去。避免這種情況的唯一方法是分配一個固定大小的數組(例如[10000]int)。

如果你剖析過你的程序堆使用情況(https://blog.golang.org/pprof
),并且想減少垃圾回收的消耗,可以將頻繁分配到堆上的變量移到棧上,可能會有較好的效果。進一步研究HotSpot JVM是如何進行逃逸分析的會是一個不錯的話題,可以參考這個鏈接,這個里面主要講解了棧分配,以及有關何時可以消除同步的檢測。

感謝你能夠認真閱讀完這篇文章,希望小編分享的“golang逃逸的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

红安县| 肇东市| 分宜县| 马关县| 台北县| 尼玛县| 宣城市| 宁河县| 岗巴县| 永泰县| 九寨沟县| 韶山市| 桦川县| 柞水县| 阳谷县| 黔东| 永兴县| 铜川市| 达拉特旗| 巴中市| 祥云县| 民勤县| 永春县| 湖口县| 霞浦县| 蒙阴县| 平阴县| 桐乡市| 泌阳县| 三门县| 泉州市| 板桥市| 榆中县| 佛教| 尚义县| 合作市| 天峻县| 郸城县| 乐陵市| 郎溪县| 图木舒克市|