欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言中的逃逸分析究竟是什么?

 更新時(shí)間:2021年09月30日 15:06:14   作者:路由器沒有路  
這篇文章主要介紹了Go語言中的逃逸,套喲究竟是什么呢?通俗來講,當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生了“逃逸”。下面文章將詳細(xì)介紹Go語言中的逃逸,需要的朋友可以參考一下

1、逃逸分析介紹

學(xué)計(jì)算機(jī)的同學(xué)都知道,在編譯原理中,分析指針動(dòng)態(tài)范圍的方法稱之為逃逸分析。通俗來講,當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生了“逃逸”。

Go語言的逃逸分析是編譯器執(zhí)行靜態(tài)代碼分析后,對(duì)內(nèi)存管理進(jìn)行的優(yōu)化和簡(jiǎn)化,它可以決定一個(gè)變量是分配到堆還棧上。

寫過C/C++的小伙伴應(yīng)該知道,使用比較經(jīng)典的mallocnew函數(shù)可以在堆上分配一塊內(nèi)存,這塊內(nèi)存的使用和回收(銷毀)的任務(wù)在程序員中,處理不當(dāng),很可能會(huì)發(fā)生內(nèi)存泄露。

2、Go中內(nèi)存分配在哪里?

但是在Go語言中,基本不用擔(dān)心內(nèi)存泄露的問題,因?yàn)閮?nèi)存回收Go語言中已經(jīng)幫我們處理了(GC回收機(jī)制)。雖然也有new函數(shù),但是使用new函數(shù)得到的內(nèi)存不一定就在堆上。堆和棧的區(qū)別對(duì)程序員“模糊化”了,當(dāng)然這一切都是Go編譯器在背后幫我們完成的。

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

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

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

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

編譯器會(huì)根據(jù)變量是否被外部引用來決定是否逃逸:

  • 如果在函數(shù)外面沒有引用到,則優(yōu)先放到棧區(qū)中;
  • 如果在函數(shù)外面存在引用的可能,則就會(huì)放到堆區(qū)中;

當(dāng)我們寫C/C++代碼時(shí),為了提高效率,會(huì)經(jīng)常將pass-by-value(傳值)提升成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ì)被銷毀,任何對(duì)這個(gè)返回值作的動(dòng)作(如解引用),都將擾亂程序的運(yùn)行,甚至導(dǎo)致程序直接崩潰。比如下面的這段代碼:

int *foo ( void )   
{   
    int t = 3;
    return &t;
}

有些同學(xué)可能知道上面這個(gè)坑,用了個(gè)更聰明的做法:在函數(shù)內(nèi)部使用new函數(shù)構(gòu)造一個(gè)變量(動(dòng)態(tài)內(nèi)存分配),然后返回此變量的地址。因?yàn)樽兞渴窃诙焉蟿?chuàng)建的,所以函數(shù)退出時(shí)不會(huì)被銷毀。

但是,這樣就行了嗎?new出來的對(duì)象該在何時(shí)何地delete呢?調(diào)用者可能會(huì)忘記delete或者直接拿返回值傳給其他函數(shù),之后就再也不能delete它了,也就是發(fā)生了內(nèi)存泄露。關(guān)于這個(gè)坑,大家可以去看看《Effective C++》條款21,講得非常好!

3、Go與C++內(nèi)存分配的區(qū)別

上面講的C/C++中會(huì)遇到的問題,在Go中作為一個(gè)語言特性被大力推崇,可以解決以上的難點(diǎn)!

C/C++中的動(dòng)態(tài)分配的內(nèi)存需要我們手動(dòng)來釋放,這樣會(huì)帶來一個(gè)問題:有些內(nèi)存處理不當(dāng)或回收不及時(shí),導(dǎo)致內(nèi)存泄露。

但是這樣的好處是:開發(fā)人員可以自己管理內(nèi)存。

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

4、逃逸分析騷操作

逃逸分析這種“騷操作”把變量合理地分配到它該去的地方。即使你是用new申請(qǐng)到的內(nèi)存,如果我發(fā)現(xiàn)你竟然在退出函數(shù)后沒有用了,那么就把你丟到棧上,畢竟棧上的內(nèi)存分配比堆上快很多;反之,即使你表面上只是一個(gè)普通的變量,但是經(jīng)過逃逸分析后發(fā)現(xiàn)在退出函數(shù)之后還有其他地方在引用,那我就把你分配到堆上。

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

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

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

5、逃逸分析引申示例說明

引申1:如何查看某個(gè)變量是否發(fā)生了逃逸?兩種方法:使用go命令,查看逃逸分析結(jié)果;反匯編源碼;

比如用這個(gè)例子:

package main
import "fmt"
func foo() *int {
    t := 3
    return &t;
}
func main() {
    x := foo()
    fmt.Println(*x)
}

使用go命令:

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


加-l是為了不讓foo函數(shù)被內(nèi)聯(lián)。得到如下輸出:

# 命令行變量
src/main.go:7:9: &t escapes to heap
src/main.go:6:7: moved to heap: t
src/main.go:12:14: *x escapes to heap
src/main.go:12:13: main ... argument does not escape

foo函數(shù)里的變量t逃逸了,和我們預(yù)想的一致。讓我們不解的是為什么main函數(shù)里的x也逃逸了?這是因?yàn)橛行┖瘮?shù)參數(shù)為interface類型,比如fmt.Println(a …interface{}) ,編譯期間很難確定其參數(shù)的具體類型,也會(huì)發(fā)生逃逸。

反匯編代碼比較難理解,這里就不講了。

引申2:下面代碼中的變量發(fā)生逃逸了嗎?

先來看示例1:

package main
type S struct {}
func main() {
  var x S
  _ = identity(x)
}
func identity(x S) S {
  return x
}

分析:Go語言函數(shù)傳遞都是通過值的,調(diào)用函數(shù)的時(shí)候,直接在棧上copy出一份參數(shù),不存在逃逸。

 再來看示例二:

package main
type S struct {}
func main() {
  var x S
  y := &x
  _ = *identity(y)
}
func identity(z *S) *S {
  return z
}

分析:identity函數(shù)的輸入直接當(dāng)成返回值了,因?yàn)闆]有對(duì)z作引用,所以z沒有逃逸。對(duì)x的引用也沒有逃出main函數(shù)的作用域,因此x也沒有發(fā)生逃逸。

 繼續(xù)看示例三:

package main
type S struct {}
func main() {
  var x S
  _ = *ref(x)
}
func ref(z S) *S {
  return &z
}

分析:z是對(duì)x的拷貝,ref函數(shù)中對(duì)z取了引用,所以z不能放在棧上,否則在ref函數(shù)之外,通過引用如何找到z,所以z必須要逃逸到堆上。僅管在main函數(shù)中,直接丟棄了ref的結(jié)果,但是Go的編譯器還沒有那么智能,分析不出來這種情況。而對(duì)x從來就沒有取引用,所以x不會(huì)發(fā)生逃逸。

還有示例四:如果對(duì)一個(gè)結(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
}

分析:refStruct函數(shù)對(duì)y取了引用,所以y發(fā)生了逃逸。

 最后看示例五:

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
}

分析:main函數(shù)里對(duì)i取了引用,并且把它傳給了refStruct函數(shù),i的引用一直在main函數(shù)的作用域用,因此i沒有發(fā)生逃逸。和上一個(gè)例子相比,有一點(diǎn)小差別,但是導(dǎo)致的程序效果是不同的:例子4中,i先在main的棧幀中分配,之后又在refStruct棧幀中分配,然后又逃逸到堆上,到堆上分配了一次,共3次分配。本例中,i只分配了一次,然后通過引用傳遞。

到此這篇關(guān)于Go語言中的逃逸分析究竟是什么?的文章就介紹到這了,更多相關(guān)Go語言中的逃逸內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法

    Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法

    這篇文章主要介紹了Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法,示例都是使用os包下的函數(shù),需要的朋友可以參考下
    2015-10-10
  • go語言中的數(shù)組指針和指針數(shù)組的區(qū)別小結(jié)

    go語言中的數(shù)組指針和指針數(shù)組的區(qū)別小結(jié)

    本文主要介紹了go語言中的數(shù)組指針和指針數(shù)組的區(qū)別小結(jié),文中通過示例代碼介紹的很詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-10-10
  • golang如何自定義json序列化應(yīng)用詳解

    golang如何自定義json序列化應(yīng)用詳解

    son格式可以算我們?nèi)粘W畛S玫男蛄谢袷街涣?,下面這篇文章主要給大家介紹了關(guān)于golang如何自定義json序列化應(yīng)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧
    2018-08-08
  • Golang項(xiàng)目在github創(chuàng)建release后自動(dòng)生成二進(jìn)制文件的方法

    Golang項(xiàng)目在github創(chuàng)建release后自動(dòng)生成二進(jìn)制文件的方法

    這篇文章主要介紹了Golang項(xiàng)目在github創(chuàng)建release后如何自動(dòng)生成二進(jìn)制文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • 在goland中配置gofmt的操作

    在goland中配置gofmt的操作

    這篇文章主要介紹了在goland中配置gofmt的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang獲取客戶端ip的實(shí)現(xiàn)

    golang獲取客戶端ip的實(shí)現(xiàn)

    本文主要介紹了golang獲取客戶端ip的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • golang如何操作csv文件詳解

    golang如何操作csv文件詳解

    這篇文章主要給大家介紹了關(guān)于golang如何操作csv文件的相關(guān)資料,以及使用Golang導(dǎo)出CSV數(shù)據(jù)并解決數(shù)據(jù)亂碼問題的解決辦法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • 基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)

    基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)

    這篇文章主要介紹了基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Go語言 init函數(shù)的具體使用

    Go語言 init函數(shù)的具體使用

    init()函數(shù)是Go語言中一種特殊的函數(shù),用于在包被導(dǎo)入時(shí)執(zhí)行一次性的初始化操作,本文就來介紹一下Go語言 init函數(shù)的具體使用,感興趣的可以了解一下
    2024-09-09
  • gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    這篇文章主要為大家介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論