一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語(yǔ)言編程技術(shù)及教程分享平臺(tái)!
分類導(dǎo)航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務(wù)器之家 - 腳本之家 - Golang - GoLang 逃逸分析的機(jī)制詳解

GoLang 逃逸分析的機(jī)制詳解

2020-06-03 11:03簾外五更風(fēng) Golang

這篇文章主要介紹了GoLang-逃逸分析的機(jī)制詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

對(duì)于手動(dòng)管理內(nèi)存的語(yǔ)言,比如 C/C++,調(diào)用著名的malloc和new函數(shù)可以在堆上分配一塊內(nèi)存,這塊內(nèi)存的使用和銷(xiāo)毀的責(zé)任都在程序員。一不小心,就會(huì)發(fā)生內(nèi)存泄露,搞得膽戰(zhàn)心驚。

但是 Golang 并不是這樣,雖然 Golang 語(yǔ)言里面也有 new。Golang 編譯器決定變量應(yīng)該分配到什么地方時(shí)會(huì)進(jìn)行逃逸分析。使用new函數(shù)得到的內(nèi)存不一定就在堆上。堆和棧的區(qū)別對(duì)程序員“模糊化”了,當(dāng)然這一切都是Go編譯器在背后幫我們完成的。一個(gè)變量是在堆上分配,還是在棧上分配,是經(jīng)過(guò)編譯器的逃逸分析之后得出的結(jié)論。

一、 逃逸分析是什么

wiki定義

In compiler optimization, escape analysis is a method for determining the dynamic scope of pointers - where in the program a pointer can be accessed. It is related to pointer analysis and shape analysis.

When a variable (or an object) is allocated in a subroutine, a pointer to the variable can escape to other threads of execution, or to calling subroutines. If an implementation uses tail call optimization (usually required for functional languages), objects may also be seen as escaping to called subroutines. If a language supports first-class continuations (as do Scheme and Standard ML of New Jersey), portions of the call stack may also escape.

If a subroutine allocates an object and returns a pointer to it, the object can be accessed from undetermined places in the program — the pointer has "escaped". Pointers can also escape if they are stored in global variables or other data structures that, in turn, escape the current procedure.

Escape analysis determines all the places where a pointer can be stored and whether the lifetime of the pointer can be proven to be restricted only to the current procedure and/or threa.

C/C++中,有時(shí)為了提高效率,常常將pass-by-value(傳值)“升級(jí)”成pass-by-reference,企圖避免構(gòu)造函數(shù)的運(yùn)行,并且直接返回一個(gè)指針。然而這里隱藏了一個(gè)很大的坑:在函數(shù)內(nèi)部定義了一個(gè)局部變量,然后返回這個(gè)局部變量的地址(指針)。這些局部變量是在棧上分配的(靜態(tài)內(nèi)存分配),一旦函數(shù)執(zhí)行完畢,變量占據(jù)的內(nèi)存會(huì)被銷(xiāo)毀,任何對(duì)這個(gè)返回值作的動(dòng)作(如解引用),都將擾亂程序的運(yùn)行,甚至導(dǎo)致程序直接崩潰。例如:

?
1
2
3
4
5
int *foo ( void ) 
  int t = 3;
  return &t;
}

為了避免這個(gè)坑,有個(gè)更聰明的做法:在函數(shù)內(nèi)部使用new函數(shù)構(gòu)造一個(gè)變量(動(dòng)態(tài)內(nèi)存分配),然后返回此變量的地址。因?yàn)樽兞渴窃诙焉蟿?chuàng)建的,所以函數(shù)退出時(shí)不會(huì)被銷(xiāo)毀。但是,這樣就行了嗎?new出來(lái)的對(duì)象該在何時(shí)何地delete呢?調(diào)用者可能會(huì)忘記delete或者直接拿返回值傳給其他函數(shù),之后就再也不能delete它了,也就是發(fā)生了內(nèi)存泄露。關(guān)于這個(gè)坑,大家可以去看看《Effective C++》條款21,講得非常好!

C++是公認(rèn)的語(yǔ)法最復(fù)雜的語(yǔ)言,據(jù)說(shuō)沒(méi)有人可以完全掌握C++的語(yǔ)法。而這一切在Go語(yǔ)言中就大不相同了。像上面示例的C++代碼放到Go里,沒(méi)有任何問(wèn)題。

你表面的光鮮,一定是背后有很多人為你撐起的!Go語(yǔ)言里就是編譯器的逃逸分析。它是編譯器執(zhí)行靜態(tài)代碼分析后,對(duì)內(nèi)存管理進(jìn)行的優(yōu)化和簡(jiǎn)化。

在編譯原理中,分析指針動(dòng)態(tài)范圍的方法稱之為逃逸分析。通俗來(lái)講,當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生了逃逸。

更簡(jiǎn)單來(lái)說(shuō),逃逸分析決定一個(gè)變量是分配在堆上還是分配在棧上。

二、 為什么要逃逸分析

前面講的C/C++中出現(xiàn)的問(wèn)題,在Go中作為一個(gè)語(yǔ)言特性被大力推崇。真是C/C++之砒霜Go之蜜糖!

C/C++中動(dòng)態(tài)分配的內(nèi)存需要我們手動(dòng)釋放,導(dǎo)致猿們平時(shí)在寫(xiě)程序時(shí),如履薄冰。這樣做有他的好處:程序員可以完全掌控內(nèi)存。但是缺點(diǎn)也是很多的:經(jīng)常出現(xiàn)忘記釋放內(nèi)存,導(dǎo)致內(nèi)存泄露。所以,很多現(xiàn)代語(yǔ)言都加上了垃圾回收機(jī)制。

Go的垃圾回收,讓堆和棧對(duì)程序員保持透明。真正解放了程序員的雙手,讓他們可以專注于業(yè)務(wù),“高效”地完成代碼編寫(xiě)。把那些內(nèi)存管理的復(fù)雜機(jī)制交給編譯器,而程序員可以去享受生活。

逃逸分析這種“騷操作”把變量合理地分配到它該去的地方,“找準(zhǔn)自己的位置”。即使你是用new申請(qǐng)到的內(nèi)存,如果我發(fā)現(xiàn)你竟然在退出函數(shù)后沒(méi)有用了,那么就把你丟到棧上,畢竟棧上的內(nèi)存分配比堆上快很多;反之,即使你表面上只是一個(gè)普通的變量,但是經(jīng)過(guò)逃逸分析后發(fā)現(xiàn)在退出函數(shù)之后還有其他地方在引用,那我就把你分配到堆上。真正地做到“按需分配”,提前實(shí)現(xiàn)共產(chǎn)主義!

如果變量都分配到堆上,堆不像??梢宰詣?dòng)清理。它會(huì)引起Go頻繁地進(jìn)行垃圾回收,而垃圾回收會(huì)占用比較大的系統(tǒng)開(kāi)銷(xiāo)(占用CPU容量的25%)。

堆和棧相比,堆適合不可預(yù)知大小的內(nèi)存分配。但是為此付出的代價(jià)是分配速度較慢,而且會(huì)形成內(nèi)存碎片。棧內(nèi)存分配則會(huì)非常快。棧分配內(nèi)存只需要兩個(gè)CPU指令:“PUSH”和“RELEASE”,分配和釋放;而堆分配內(nèi)存首先需要去找到一塊大小合適的內(nèi)存塊,之后要通過(guò)垃圾回收才能釋放。

通過(guò)逃逸分析,可以盡量把那些不需要分配到堆上的變量直接分配到棧上,堆上的變量少了,會(huì)減輕分配堆內(nèi)存的開(kāi)銷(xiāo),同時(shí)也會(huì)減少gc的壓力,提高程序的運(yùn)行速度。

三、 逃逸分析如何完成

Go逃逸分析最基本的原則是:如果一個(gè)函數(shù)返回對(duì)一個(gè)變量的引用,那么它就會(huì)發(fā)生逃逸。

簡(jiǎn)單來(lái)說(shuō),編譯器會(huì)分析代碼的特征和代碼生命周期,Go中的變量只有在編譯器可以證明在函數(shù)返回后不會(huì)再被引用的,才分配到棧上,其他情況下都是分配到堆上。

Go語(yǔ)言里沒(méi)有一個(gè)關(guān)鍵字或者函數(shù)可以直接讓變量被編譯器分配到堆上,相反,編譯器通過(guò)分析代碼來(lái)決定將變量分配到何處。

對(duì)一個(gè)變量取地址,可能會(huì)被分配到堆上。但是編譯器進(jìn)行逃逸分析后,如果考察到在函數(shù)返回后,此變量不會(huì)被引用,那么還是會(huì)被分配到棧上。

簡(jiǎn)單來(lái)說(shuō),編譯器會(huì)根據(jù)變量是否被外部引用來(lái)決定是否逃逸:

1)如果函數(shù)外部沒(méi)有引用,則優(yōu)先放到棧中;

2) 如果函數(shù)外部存在引用,則必定放到堆中;

針對(duì)第一條,可能放到堆上的情形:定義了一個(gè)很大的數(shù)組,需要申請(qǐng)的內(nèi)存過(guò)大,超過(guò)了棧的存儲(chǔ)能力。

四、 逃逸分析實(shí)例

下面是一個(gè)簡(jiǎn)單的例子。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import ()
 
func foo() *int {
  var x int
  return &x
}
 
func bar() int {
  x := new(int)
  *x = 1
  return *x
}
 
func main() {}

開(kāi)啟逃逸分析日志很簡(jiǎn)單,只要在編譯的時(shí)候加上-gcflags '-m',但是我們?yōu)榱瞬蛔尵幾g時(shí)自動(dòng)內(nèi)連函數(shù),一般會(huì)加-l參數(shù),最終為-gcflags '-m -l',執(zhí)行如下命令:

?
1
2
3
4
5
$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:5:9: &x escapes to heap
./main.go:4:6: moved to heap: x
./main.go:9:10: bar new(int) does not escape

上面代碼中foo() 中的 x 最后在堆上分配,而 bar() 中的 x 最后分配在了棧上。

也可以使用反匯編命令看出變量是否發(fā)生逃逸。

?
1
$ go tool compile -S main.go

截取部分結(jié)果,圖中標(biāo)記出來(lái)的說(shuō)明foo中x是在堆上分配內(nèi)存,發(fā)生了逃逸。

GoLang 逃逸分析的機(jī)制詳解

反匯編命令結(jié)果

什么時(shí)候逃逸呢? golang.org FAQ 上有一個(gè)關(guān)于變量分配的問(wèn)題如下:

Q: How do I know whether a variable is allocated on the heap or the stack?

A: From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.

The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

關(guān)于什么時(shí)候逃逸,什么時(shí)候不逃逸,我們接下來(lái)再看幾個(gè)小例子。

1)Example1

?
1
2
3
4
5
6
7
8
9
10
package main
type S struct{}
func main() {
  var x S
  y := &x
  _ = *identity(y)
}
func identity(z *S) *S {
  return z
}

結(jié)果如下:

?
1
2
3
# command-line-arguments
./main.go:8:22: leaking param: z to result ~r1 level=0
./main.go:5:7: main &x does not escape

這里的第一行表示z變量是“流式”,因?yàn)閕dentity這個(gè)函數(shù)僅僅輸入一個(gè)變量,又將這個(gè)變量作為返回輸出,但identity并沒(méi)有引用z,所以這個(gè)變量沒(méi)有逃逸,而x沒(méi)有被引用,且生命周期也在mian里,x沒(méi)有逃逸,分配在棧上。

2)Example2

?
1
2
3
4
5
6
7
8
9
package main
type S struct{}
func main() {
  var x S
  _ = *ref(x)
}
func ref(z S) *S {
  return &z
}

結(jié)果如下:

?
1
2
3
# command-line-arguments
./main.go:8:9: &z escapes to heap
./main.go:7:16: moved to heap: z

這里的z是逃逸了,原因很簡(jiǎn)單,go都是值傳遞,ref函數(shù)copy了x的值,傳給z,返回z的指針,然后在函數(shù)外被引用,說(shuō)明z這個(gè)變量在函數(shù)內(nèi)聲明,可能會(huì)被函數(shù)外的其他程序訪問(wèn)。所以z逃逸了,分配在堆上

3)Example3

?
1
2
3
4
5
6
7
8
9
10
11
12
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
}

結(jié)果如下:

?
1
2
3
# command-line-arguments
./main.go:10:8: &y escapes to heap
./main.go:9:26: moved to heap: y

看日志的輸出,這里的y是逃逸了,看來(lái)在struct里好像并沒(méi)有區(qū)別,有可能被函數(shù)外的程序訪問(wèn)就會(huì)逃逸

4)Example4

?
1
2
3
4
5
6
7
8
9
10
11
12
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
}

結(jié)果如下:

?
1
2
3
# command-line-arguments
./main.go:9:27: leaking param: y to result z level=0
./main.go:7:12: main &i does not escape

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

5)Example5

?
1
2
3
4
5
6
7
8
9
10
11
12
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
}

結(jié)果如下:

?
1
2
3
4
5
6
# command-line-arguments
./main.go:10:21: leaking param: y
./main.go:10:21: ref z does not escape
./main.go:8:6: &i escapes to heap
./main.go:7:6: moved to heap: i
./main.go:8:10: main &x does not escape

這里的z沒(méi)有逃逸,而i卻逃逸了,這是因?yàn)間o的逃逸分析不知道z和i的關(guān)系,逃逸分析不知道參數(shù)y是z的一個(gè)成員,所以只能把它分配給堆。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://studygolang.com/articles/26378

延伸 · 閱讀

精彩推薦
  • Golanggo日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    go日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    這篇文章主要介紹了go日志系統(tǒng)logrus顯示文件和行號(hào)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    SmallQinYan12302021-02-02
  • GolangGolang中Bit數(shù)組的實(shí)現(xiàn)方式

    Golang中Bit數(shù)組的實(shí)現(xiàn)方式

    這篇文章主要介紹了Golang中Bit數(shù)組的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    天易獨(dú)尊11682021-06-09
  • Golanggolang如何使用struct的tag屬性的詳細(xì)介紹

    golang如何使用struct的tag屬性的詳細(xì)介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細(xì)介紹,從例子說(shuō)起,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看...

    Go語(yǔ)言中文網(wǎng)11352020-05-21
  • GolangGolang通脈之?dāng)?shù)據(jù)類型詳情

    Golang通脈之?dāng)?shù)據(jù)類型詳情

    這篇文章主要介紹了Golang通脈之?dāng)?shù)據(jù)類型,在編程語(yǔ)言中標(biāo)識(shí)符就是定義的具有某種意義的詞,比如變量名、常量名、函數(shù)名等等,Go語(yǔ)言中標(biāo)識(shí)符允許由...

    4272021-11-24
  • Golanggolang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧 ...

    李浩的life12792020-05-27
  • Golanggo語(yǔ)言制作端口掃描器

    go語(yǔ)言制作端口掃描器

    本文給大家分享的是使用go語(yǔ)言編寫(xiě)的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang 通過(guò)ssh代理連接mysql的操作

    golang 通過(guò)ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過(guò)ssh代理連接mysql的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    a165861639710342021-03-08
  • Golanggolang的httpserver優(yōu)雅重啟方法詳解

    golang的httpserver優(yōu)雅重啟方法詳解

    這篇文章主要給大家介紹了關(guān)于golang的httpserver優(yōu)雅重啟的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,...

    helight2992020-05-14
主站蜘蛛池模板: 国产成人在线免费观看 | 亚洲天堂成人在线 | 给我免费的视频在线观看 | 亚洲精品视频专区 | 女教师巨大乳孔中文字幕免费 | chinese圣水黄金调教 | 四虎1515hhh co m | 成人au免费视频影院 | naruto tube18动漫 mm131亚洲精品久久 | 免费lulu网站 | 特黄特a级特别特级特毛片 特黄a级三级三级野战 | 色综合亚洲天天综合网站 | 亚洲欧美成人综合久久久 | 久久中文字幕综合不卡一二区 | 久久婷婷五月综合色精品首页 | 三级理论在线观看 | 三级无删减高清在线影院 | 色哟哟在线观看 | 18free性欧美另类hd | 成人精品一级毛片 | 久久电影院久久国产 | 蛮荒的童话未删减在线观看 | 欧美一级专区免费大片 | a亚洲天堂 | 日本欧美不卡一区二区三区在线 | 日女人免费视频 | 99一区二区三区 | 成年私人影院免费视频网站 | 蜜桃成熟时1997在线看免费看 | 国产高清露脸学生在线观看 | 污黄漫| 99热导航| 婷婷久久热99在线精品 | 大团圆6全文在线阅读 | 91精品综合久久久久m3u8 | 精品四虎 | 亚洲va国产日韩欧美精品色婷婷 | 日韩aⅴ在线观看 | 男人操女人视频 | 884aa草莓视频 | 亚洲一二三区久久五月天婷婷 |