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

溫馨提示×

溫馨提示×

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

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

Go 語言變量分配到哪里了

發布時間:2021-10-18 16:53:06 來源:億速云 閱讀:114 作者:iii 欄目:編程語言

這篇文章主要介紹“Go 語言變量分配到哪里了”,在日常操作中,相信很多人在Go 語言變量分配到哪里了問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”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() }

開局就是一把問號,帶著問題進行學習。請問 main 調用 GetUserInfo  后返回的&User{...}。這個變量是分配到棧上了呢,還是分配到堆上了?

什么是堆/棧

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

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

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

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

什么是逃逸分析

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

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

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

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

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

在什么階段確立逃逸

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

為什么需要逃逸

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

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

  • 申請、分配、回收內存的系統開銷增大(相對于棧)。

  • 動態分配產生一定量的內存碎片。

簡單來說,就是頻繁申請并分配堆內存是有一定 “代價” 的。會影響應用程序運行的效率,間接影響到整體系統。

因此 “按需分配” 最大限度的靈活利用資源,才是正確的治理之道。這就是為什么需要逃逸分析的原因,你覺得呢?

怎么確定是否逃逸

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

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

  • -l 會禁用函數內聯,在這里禁用掉 inline 能更好的觀察逃逸情況,減少干擾。

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

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

$ go tool compile -S main.go

注:可以通過 go tool compile -help 查看所有允許傳遞給編譯器的標識參數。

逃逸案例

案例一:指針

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

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() }

執行命令觀察一下,如下:

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

通過查看分析結果,可得知 &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 指令,發現其執行了 runtime.newobject 方法,也就是確實是分配到了堆上。這是為什么呢?

分析結果

這是因為 GetUserInfo() 返回的是指針對象,引用被返回到了方法之外了。因此編譯器會把該對象分配到堆上,而不是棧上。

否則方法結束之后,局部變量就被回收了,豈不是翻車。所以最終分配到堆上是理所當然的

再想想

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

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 中,因此它沒有發生逃逸。

案例二:未確定類型

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

執行命令觀察一下,如下:

$ 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

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

分析結果

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

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

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

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

案例三、泄露參數

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"}) }

執行命令觀察一下,如下:

$ 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 是一個泄露參數。結合代碼可得知其傳給 GetUserInfo  方法后,沒有做任何引用之類的涉及變量的動作,直接就把這個變量返回出去了。

因此這個變量實際上并沒有逃逸,它的作用域還在 main() 之中,所以分配在棧上。

再想想

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

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"}) }

執行命令觀察一下,如下:

$ 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

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

到此,關于“Go 語言變量分配到哪里了”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

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

AI

临汾市| 罗定市| 温泉县| 临夏县| 福贡县| 隆子县| 彭州市| 安阳市| 厦门市| 吐鲁番市| 阳朔县| 宜城市| 蕉岭县| 丹东市| 商城县| 辉南县| 安龙县| 天台县| 通江县| 基隆市| 太和县| 东源县| 类乌齐县| 南和县| 克什克腾旗| 崇州市| 易门县| 丰宁| 黄浦区| 镶黄旗| 财经| 丹寨县| 甘孜| 龙门县| 高碑店市| 佛学| 凯里市| 襄城县| 永德县| 宝清县| 夹江县|