<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          『每周譯Go』Go如何做逃逸分析

          共 4548字,需瀏覽 10分鐘

           ·

          2021-04-23 14:20

          原文地址:http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html

          原文作者:

          本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w15_golang_escape_analysis.md

          譯者:cuua

          校對:gocn

          垃圾回收是 Go - 自動內(nèi)存管理的一個便利功能, 使代碼更整潔,內(nèi)存泄漏的可能性更小。但是,GC 還會增加間接性能消耗,因?yàn)槌绦蛐枰ㄆ谕V共⑹占词褂玫膶ο蟆o 編譯器足夠智能,可以自動決定是否應(yīng)在堆上分配變量,之后需要在堆上收集垃圾,或者是否可以將其分配為該變量的函數(shù)的棧的一部分。棧與堆分配變量不同,棧分配變量不會產(chǎn)生任何 GC 開銷,因?yàn)樗鼈冊跅5钠溆嗖糠郑ó?dāng)功能返回時)被銷毀。

          例如,Go 的逃生分析比HotSpot JVM 更基本。基本規(guī)則是,如果從申報的函數(shù)返回對變量的引用,則會"逃逸" - 函數(shù)返回后可以引用該變量,因此必須將其堆分配。這是比較復(fù)雜的,因?yàn)椋?/p>

          • 調(diào)用其他功能的函數(shù)
          • 分配給結(jié)構(gòu)體成員的引用
          • 切片和maps
          • cgo將指針指向變量

          為了執(zhí)行逃生分析,Go 在編譯時構(gòu)建一個函數(shù)調(diào)用圖,并跟蹤輸入?yún)?shù)和返回值的流。函數(shù)可能引用其中一個參數(shù),但如果該引用未返回,變量不會逃逸。函數(shù)也可以返回引用,但在申明變量返回的函數(shù)之前,該引用可能由棧中的另一個函數(shù)取消引用或未返回。為了說明一些簡單的案例,我們可以運(yùn)行編譯器,這將打印詳細(xì)的逃生分析信息:-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' '-l'標(biāo)簽阻止功能被內(nèi)聯(lián) (這是另一個時間的主題) 來構(gòu)建這個功能。輸出是:什么都沒有!Go 使用值傳遞,因此始終將變量復(fù)制到棧中。在沒有引用的一般代碼中,總是很少使用棧分配。沒有逃生分析可做。再看下面一個例子:

          package main

          type S struct {}

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

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

          輸出:

          $ go run -gcflags '-m -l' main.go
          # command-line-arguments
          .\main.go:11:15: leaking param: z to result ~r1 level=0

          第一行顯示變量"流過":輸入變量返回為輸出。但不采取參考,所以變量不會逃逸。不在main返回之后沒有對x的引用存在,因此x分配在main的堆上。第三個實(shí)驗(yàn):


          package main

          type S struct {}

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

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

          輸出:


          $ go run -gcflags '-m -l' main.go
          # command-line-arguments
          .\main.go:10:10: moved to heap: z

          現(xiàn)在有一些逃避正在發(fā)生。請記住,go是值傳遞,所以z是main中x變量的副本。返回z的引用,所以z不能是棧的一部分-返回時的參考點(diǎn)在哪里?取而代之的是它逃到堆。盡管 Go 在不取消計算參考值的情況下會立即扔掉引用,但 Go 的逃逸分析不夠精密,無法找出這一點(diǎn) - 它只查看輸入和返回變量的流。值得注意的是,在這種情況下,如果我們不阻止它,編譯器就會強(qiáng)調(diào)這一點(diǎn)。

          如果將引用分配給結(jié)構(gòu)成員,該怎么辦?


          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
          }

          輸出:


          $ go run -gcflags '-m -l' main.go
          # command-line-arguments
          .\main.go:13:16: moved to heap: y

          在這種情況下,Go 仍然可以跟蹤引用流,即使引用是結(jié)構(gòu)體的成員。既然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
          }

          輸出:


          $ go run -gcflags '-m -l' main.go
          # command-line-arguments
          .\main.go:13:16: leaking param: y to result z level=0

          由于main做了引用并傳遞refStruct,引用永遠(yuǎn)不會超過申報引用變量的棧。這和前面的程序有稍微不同的語義,但如果第二個程序足夠的話,它會更有效率:在第一個例子i必須分配在main的棧上,然后在堆上重新分配并將其復(fù)制為refStruct的參數(shù)。在第二個示例中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
          }

          輸出:


          $ go run -gcflags '-m -l' main.go
          # command-line-arguments
          .\main.go:14:10: leaking param: y
          .\main.go:14:18: z does not escape
          .\main.go:10:6: moved to heap: i

          這里的問題是 y 是分配給輸入結(jié)構(gòu)體的成員。Go 無法跟蹤該關(guān)系 - 輸入僅允許流到輸出 - 因此逃逸分析失敗,必須對變量進(jìn)行堆分配。有許多有據(jù)可查的案例(as of Go 1.5),由于go逃逸分析的限制,必須堆分配變量 -請參閱此鏈接(https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview) 。

          最后,maps和切片呢?請記住,maps和切片實(shí)際上只是使用指針構(gòu)建到堆分配的內(nèi)存:切片結(jié)構(gòu)暴露在包中(SliceHeader : https://golang.org/pkg/reflect/#SliceHeader)中。map結(jié)構(gòu)是更難找到的,但它存在:hmap 。如果這些結(jié)構(gòu)無法逃逸,它們將被棧分配,但備份數(shù)組或哈希存儲桶中的數(shù)據(jù)本身將每次都堆分配。避免這種情況的唯一方法是分配一個固定大小的數(shù)組(如[10000]int)。

          如果您已經(jīng)看過分析程序的堆使用情況 ,并且需要減少 GC 時間,則可能會從堆中移動頻繁分配的變量而獲得一些收獲。這也只是一個引人入勝的話題:要進(jìn)一步閱讀 HotSpot JVM 如何處理逃逸分析,請查看這篇文章(https://www.cc.gatech.edu/~harrold/6340/cs6340_fall2009/Readings/choi99escape.pdf) ,其中涉及堆棧分配,以及檢測何時可以消除同步。

          www.gopherchina.org 還有 Gopher China 2021 重磅來襲  ,期待 Gopher 們的到來!!!



          瀏覽 53
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产色亚洲 | 日本无码视频在线播放 | 久久亚洲Av夜福利精品一区 | 骚B视频 欧美大香蕉在线片 | 日韩免费福利视频 |