成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

我要在棧上。不,你應(yīng)該在堆上

lily_wang / 3359人閱讀

摘要:原文地址我要在棧上。很核心的一點就是它有沒有被作用域之外所引用,而這里作用域仍然保留在中,因此它沒有發(fā)生逃逸案例二未確定類型執(zhí)行命令觀察一下,如下通過查看分析結(jié)果,可得知變量逃到了堆上,也就是該對象在堆上分配。

原文地址:我要在棧上。不,你應(yīng)該在堆上

前言

我們在寫代碼的時候,有時候會想這個變量到底分配到哪里了?這時候可能會有人說,在棧上,在堆上。信我準(zhǔn)沒錯...

但從結(jié)果上來講你還是一知半解,這可不行,萬一被人懵了呢。今天我們一起來深挖下 Go 在這塊的奧妙,自己動手豐衣足食

問題
type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo() *User {
    return &User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"}
}

func main() {
    _ = GetUserInfo()
}

開局就是一把問號,帶著問題進(jìn)行學(xué)習(xí)。請問 main 調(diào)用 GetUserInfo 后返回的 &User{...}。這個變量是分配到棧上了呢,還是分配到堆上了?

什么是堆/棧

在這里并不打算詳細(xì)介紹堆棧,僅簡單介紹本文所需的基礎(chǔ)知識。如下:

堆(Heap):一般來講是人為手動進(jìn)行管理,手動申請、分配、釋放。一般所涉及的內(nèi)存大小并不定,一般會存放較大的對象。另外其分配相對慢,涉及到的指令動作也相對多

棧(Stack):由編譯器進(jìn)行管理,自動申請、分配、釋放。一般不會太大,我們常見的函數(shù)參數(shù)(不同平臺允許存放的數(shù)量不同),局部變量等等都會存放在棧上

今天我們介紹的 Go 語言,它的堆棧分配是通過 Compiler 進(jìn)行分析,GC 去管理的,而對其的分析選擇動作就是今天探討的重點

什么是逃逸分析

在編譯程序優(yōu)化理論中,逃逸分析是一種確定指針動態(tài)范圍的方法,簡單來說就是分析在程序的哪些地方可以訪問到該指針

通俗地講,逃逸分析就是確定一個變量要放堆上還是棧上,規(guī)則如下:

是否有在其他地方(非局部)被引用。只要有可能被引用了,那么它一定分配到堆上。否則分配到棧上

即使沒有被外部引用,但對象過大,無法存放在棧區(qū)上。依然有可能分配到堆上

對此你可以理解為,逃逸分析是編譯器用于決定變量分配到堆上還是棧上的一種行為

在什么階段確立逃逸

在編譯階段確立逃逸,注意并不是在運行時

為什么需要逃逸

這個問題我們可以反過來想,如果變量都分配到堆上了會出現(xiàn)什么事情?例如:

垃圾回收(GC)的壓力不斷增大

申請、分配、回收內(nèi)存的系統(tǒng)開銷增大(相對于棧)

動態(tài)分配產(chǎn)生一定量的內(nèi)存碎片

其實總的來說,就是頻繁申請、分配堆內(nèi)存是有一定 “代價” 的。會影響應(yīng)用程序運行的效率,間接影響到整體系統(tǒng)。因此 “按需分配” 最大限度的靈活利用資源,才是正確的治理之道。這就是為什么需要逃逸分析的原因,你覺得呢?

怎么確定是否逃逸

第一,通過編譯器命令,就可以看到詳細(xì)的逃逸分析過程。而指令集 -gcflags 用于將標(biāo)識參數(shù)傳遞給 Go 編譯器,涉及如下:

-m 會打印出逃逸分析的優(yōu)化策略,實際上最多總共可以用 4 個 -m,但是信息量較大,一般用 1 個就可以了

-l 會禁用函數(shù)內(nèi)聯(lián),在這里禁用掉 inline 能更好的觀察逃逸情況,減少干擾

$ go build -gcflags "-m -l" main.go

第二,通過反編譯命令查看

$ go tool compile -S main.go

注:可以通過 go tool compile -help 查看所有允許傳遞給編譯器的標(biāo)識參數(shù)

逃逸案例 案例一:指針

第一個案例是一開始拋出的問題,現(xiàn)在你再看看,想想,如下:

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo() *User {
    return &User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"}
}

func main() {
    _ = GetUserInfo()
}

執(zhí)行命令觀察一下,如下:

$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:10:54: &User literal escapes to heap

通過查看分析結(jié)果,可得知 &User 逃到了堆里,也就是分配到堆上了。這是不是有問題啊...再看看匯編代碼確定一下,如下:

$ go tool compile -S main.go                
"".GetUserInfo STEXT size=190 args=0x8 locals=0x18
    0x0000 00000 (main.go:9)    TEXT    "".GetUserInfo(SB), $24-8
    ...
    0x0028 00040 (main.go:10)    MOVQ    AX, (SP)
    0x002c 00044 (main.go:10)    CALL    runtime.newobject(SB)
    0x0031 00049 (main.go:10)    PCDATA    $2, $1
    0x0031 00049 (main.go:10)    MOVQ    8(SP), AX
    0x0036 00054 (main.go:10)    MOVQ    $13746731, (AX)
    0x003d 00061 (main.go:10)    MOVQ    $7, 16(AX)
    0x0045 00069 (main.go:10)    PCDATA    $2, $-2
    0x0045 00069 (main.go:10)    PCDATA    $0, $-2
    0x0045 00069 (main.go:10)    CMPL    runtime.writeBarrier(SB), $0
    0x004c 00076 (main.go:10)    JNE    156
    0x004e 00078 (main.go:10)    LEAQ    go.string."EDDYCJY"(SB), CX
    ...

我們將目光集中到 CALL 指令,發(fā)現(xiàn)其執(zhí)行了 runtime.newobject 方法,也就是確實是分配到了堆上。這是為什么呢?

分析結(jié)果

這是因為 GetUserInfo() 返回的是指針對象,引用被返回到了方法之外了。因此編譯器會把該對象分配到堆上,而不是棧上。否則方法結(jié)束之后,局部變量就被回收了,豈不是翻車。所以最終分配到堆上是理所當(dāng)然的

再想想

那你可能會想,那就是所有指針對象,都應(yīng)該在堆上?并不。如下:

func main() {
    str := new(string)
    *str = "EDDYCJY"
}

你想想這個對象會分配到哪里?如下:

$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:4:12: main new(string) does not escape

顯然,該對象分配到棧上了。很核心的一點就是它有沒有被作用域之外所引用,而這里作用域仍然保留在 main 中,因此它沒有發(fā)生逃逸

案例二:未確定類型
func main() {
    str := new(string)
    *str = "EDDYCJY"

    fmt.Println(str)
}

執(zhí)行命令觀察一下,如下:

$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:9:13: str escapes to heap
./main.go:6:12: new(string) escapes to heap
./main.go:9:13: main ... argument does not escape

通過查看分析結(jié)果,可得知 str 變量逃到了堆上,也就是該對象在堆上分配。但上個案例時它還在棧上,我們也就 fmt 輸出了它而已。這...到底發(fā)生了什么事?

分析結(jié)果

相對案例一,案例二只加了一行代碼 fmt.Println(str),問題肯定出在它身上。其原型:

func Println(a ...interface{}) (n int, err error)

通過對其分析,可得知當(dāng)形參為 interface 類型時,在編譯階段編譯器無法確定其具體的類型。因此會產(chǎn)生逃逸,最終分配到堆上

如果你有興趣追源碼的話,可以看下內(nèi)部的 reflect.TypeOf(arg).Kind() 語句,其會造成堆逃逸,而表象就是 interface 類型會導(dǎo)致該對象分配到堆上

案例三、泄露參數(shù)
type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo(u *User) *User {
    return u
}

func main() {
    _ = GetUserInfo(&User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"})
}

執(zhí)行命令觀察一下,如下:

$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:9:18: leaking param: u to result ~r1 level=0
./main.go:14:63: main &User literal does not escape

我們注意到 leaking param 的表述,它說明了變量 u 是一個泄露參數(shù)。結(jié)合代碼可得知其傳給 GetUserInfo 方法后,沒有做任何引用之類的涉及變量的動作,直接就把這個變量返回出去了。因此這個變量實際上并沒有逃逸,它的作用域還在 main() 之中,所以分配在棧上

再想想

那你再想想怎么樣才能讓它分配到堆上?結(jié)合案例一,舉一反三。修改如下:

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo(u User) *User {
    return &u
}

func main() {
    _ = GetUserInfo(User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"})
}

執(zhí)行命令觀察一下,如下:

$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:10:9: &u escapes to heap
./main.go:9:18: moved to heap: u

只要一小改,它就考慮會被外部所引用,因此妥妥的分配到堆上了

總結(jié)

在本文我給你介紹了逃逸分析的概念和規(guī)則,并列舉了一些例子加深理解。但實際肯定遠(yuǎn)遠(yuǎn)不止這些案例,你需要做到的是掌握方法,遇到再看就好了。除此之外你還需要注意:

靜態(tài)分配到棧上,性能一定比動態(tài)分配到堆上好

底層分配到堆,還是棧。實際上對你來說是透明的,不需要過度關(guān)心

每個 Go 版本的逃逸分析都會有所不同(會改變,會優(yōu)化)

直接通過 go build -gcflags "-m -l" 就可以看到逃逸分析的過程和結(jié)果

到處都用指針傳遞并不一定是最好的,要用對

之前就有想過要不要寫 “逃逸分析” 相關(guān)的文章,直到最近看到在夜讀里有人問,還是有寫的必要。對于這塊的知識點。我的建議是適當(dāng)了解,但沒必要硬記??炕A(chǔ)知識點加命令調(diào)試觀察就好了。像是曹大之前講的 “你琢磨半天逃逸分析,一壓測,瓶頸在鎖上”,完全沒必要過度在意...

參考

Golang escape analysis

FAQ

逃逸分析

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/31543.html

相關(guān)文章

  • Nginx 源碼分析:ngx_array_t

    摘要:源文件路徑版本主要作用分析是內(nèi)部使用的數(shù)組型數(shù)據(jù)結(jié)構(gòu),與語言內(nèi)置的數(shù)組概念上類似,但是有兩點主要區(qū)別使用內(nèi)存池來管理內(nèi)存雖然有預(yù)設(shè)數(shù)組大小的概念,但是在數(shù)組元素超出預(yù)設(shè)值大小時,會在內(nèi)存池中發(fā)生重分配。 源文件路徑 版本:1.8.0 srccoreNgx_array.h srccoreNgx_array.c 主要作用分析 ngx_array_t是Nginx內(nèi)部使用的數(shù)組型數(shù)據(jù)...

    zhonghanwen 評論0 收藏0
  • 萬萬沒想到,JVM內(nèi)存結(jié)構(gòu)的面試題可以問的這么難?

    摘要:方法區(qū)在實際內(nèi)存空間站可以是不連續(xù)的。這一規(guī)定,可以說是給了虛擬機(jī)廠商很大的自由。但是值得注意的是,堆其實還未每一個線程單獨分配了一塊空間,這部分空間在分配時是線程獨享的,在使用時是線程共享的。 在我的博客中,之前有很多文章介紹過JVM內(nèi)存結(jié)構(gòu),相信很多看多我文章的朋友對這部分知識都有一定的了解了。 那么,請大家嘗試著回答一下以下問題: 1、JVM管理的內(nèi)存結(jié)構(gòu)是怎樣的? 2、不同的...

    CloudwiseAPM 評論0 收藏0
  • JAVA中堆和棧的區(qū)別

    摘要:堆內(nèi)存主要作用是存放運行時創(chuàng)建的對象。堆內(nèi)存用來存放由創(chuàng)建的對象和數(shù)組,在堆中分配的內(nèi)存,由虛擬機(jī)的自動垃圾回收器來管理。這也是比較占內(nèi)存的原因,實際上,棧中的變量指向堆內(nèi)存中的變量,這就是中的指針 堆:(對象) 引用類型的變量,其內(nèi)存分配在堆上或者常量池(字符串常量、基本數(shù)據(jù)類型常量),需要通過new等方式來創(chuàng)建。 堆內(nèi)存主要作用是存放運行時創(chuàng)建(new)的對象。(主要用于存放對象,...

    RyanQ 評論0 收藏0

發(fā)表評論

0條評論

lily_wang

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<