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

一文帶你搞懂golang中內(nèi)存分配逃逸分析

 更新時(shí)間:2023年08月31日 09:07:58   作者:jimshi  
這篇文章主要帶大家一起學(xué)習(xí)一下golang中內(nèi)存分配逃逸分析,文中的示例代碼講解詳細(xì),對(duì)我們深入了解golang有一定的幫助,感興趣的小伙伴可以了解下

一. golang 的內(nèi)存分配逃逸

1. 關(guān)于堆和棧

棧 可以簡(jiǎn)單得理解成一次函數(shù)調(diào)用內(nèi)部申請(qǐng)到的內(nèi)存,它們會(huì)隨著函數(shù)的返回把內(nèi)存還給系統(tǒng)。

func F() {
	temp := make([]int, 0, 20)
	...
}

類似于上面代碼里面的temp變量,只是內(nèi)函數(shù)內(nèi)部申請(qǐng)的臨時(shí)變量,并不會(huì)作為返回值返回,它就是被編譯器申請(qǐng)到棧里面。

申請(qǐng)到 棧內(nèi)存 好處:函數(shù)返回直接釋放,不會(huì)引起垃圾回收,對(duì)性能沒有影響。

再來看看堆得情況之一如下代碼:

func F() []int{
	a := make([]int, 0, 20)
	return a
}

而上面這段代碼,申請(qǐng)的代碼一模一樣,但是申請(qǐng)后作為返回值返回了,編譯器會(huì)認(rèn)為變量之后還會(huì)被使用,當(dāng)函數(shù)返回之后并不會(huì)將其內(nèi)存歸還,那么它就會(huì)被申請(qǐng)到 堆 上面了。

申請(qǐng)到堆上面的內(nèi)存才會(huì)引起垃圾回收,如果這個(gè)過程(特指垃圾回收不斷被觸發(fā))過于高頻就會(huì)導(dǎo)致 gc 壓力過大,程序性能出問題。

我們?cè)倏纯慈缦聨讉€(gè)例子:

func F() {
	a := make([]int, 0, 20)     // 棧 空間小
	b := make([]int, 0, 20000) // 堆 空間過大
	l := 20
	c := make([]int, 0, l) // 堆 動(dòng)態(tài)分配不定空間
}

像是 b 這種 即使是臨時(shí)變量,申請(qǐng)過大也會(huì)在堆上面申請(qǐng)。

對(duì)于 c 編譯器對(duì)于這種不定長(zhǎng)度的申請(qǐng)方式,也會(huì)在堆上面申請(qǐng),即使申請(qǐng)的長(zhǎng)度很短。

2. 逃逸分析(Escape analysis)

所謂逃逸分析(Escape analysis)是指由編譯器決定內(nèi)存分配的位置,不需要程序員指定。

在函數(shù)中申請(qǐng)一個(gè)新的對(duì)象:

  • 如果分配 在棧中,則函數(shù)執(zhí)行結(jié)束可自動(dòng)將內(nèi)存回收;
  • 如果分配在堆中,則函數(shù)執(zhí)行結(jié)束可交給GC(垃圾回收)處理;

注意,對(duì)于函數(shù)外部沒有引用的對(duì)象,也有可能放到堆中,比如內(nèi)存過大超過棧的存儲(chǔ)能力。

3. 逃逸場(chǎng)景(什么情況才分配到堆中)

3.1 指針逃逸

Go可以返回局部變量指針,這其實(shí)是一個(gè)典型的變量逃逸案例,示例代碼如下:

package main
type Student struct {
    Name string
    Age  int
}
func StudentRegister(name string, age int) *Student {
    s := new(Student) //局部變量s逃逸到堆
    s.Name = name
    s.Age = age
    return s
}
func main() {
    StudentRegister("Jim", 18)
}

雖然 在函數(shù) StudentRegister() 內(nèi)部 s 為局部變量,其值通過函數(shù)返回值返回,s 本身為一指針,其指向的內(nèi)存地址不會(huì)是棧而是堆,這就是典型的逃逸案例。

終端運(yùn)行命令查看逃逸分析日志:

go build -gcflags=-m

可見在StudentRegister()函數(shù)中,也即代碼第9行顯示”escapes to heap”,代表該行內(nèi)存分配發(fā)生了逃逸現(xiàn)象。

3.2 ??臻g不足逃逸(空間開辟過大)

package main
func Slice() {
    s := make([]int, 1000, 1000)
    for index, _ := range s {
        s[index] = index
    }
}
func main() {
    Slice()
}

上面代碼Slice()函數(shù)中分配了一個(gè)1000個(gè)長(zhǎng)度的切片,是否逃逸取決于??臻g是否足夠大。 直接查看編譯提示,如下:

所以只是1000的長(zhǎng)度還不足以發(fā)生逃逸現(xiàn)象。然后就x10倍吧

package main
func Slice() {
    s := make([]int, 10000, 10000)
    for index, _ := range s {
        s[index] = index
    }
}
func main() {
    Slice()
}

分析如下:

當(dāng)切片長(zhǎng)度擴(kuò)大到10000時(shí)就會(huì)逃逸。

實(shí)際上當(dāng)??臻g不足以存放當(dāng)前對(duì)象時(shí)或無(wú)法判斷當(dāng)前切片長(zhǎng)度時(shí)會(huì)將對(duì)象分配到堆中。

3.3 動(dòng)態(tài)類型逃逸(不確定長(zhǎng)度大?。?/strong>

很多函數(shù)參數(shù)為interface類型,比如fmt.Println(a …interface{}),編譯期間很難確定其參數(shù)的具體類型,也能產(chǎn)生逃逸。

如下代碼所示:

package main
import "fmt"
func main() {
    s := "Escape"
    fmt.Println(s)
}

逃逸分下如下:

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: s escapes to heap
.\main.go:7: main ... argument does not escape

又或者像前面提到的例子:

func F() {
	a := make([]int, 0, 20)     // 棧 空間小
	b := make([]int, 0, 20000) // 堆 空間過大 逃逸
	l := 20
	c := make([]int, 0, l) // 堆 動(dòng)態(tài)分配不定空間 逃逸
}

3.4 閉包引用對(duì)象逃逸

Fibonacci數(shù)列的函數(shù):

package main
import "fmt"
func Fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}
func main() {
    f := Fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Printf("Fibonacci: %d\n", f())
    }
}

輸出如下:

~/go/src/gitHub/test/pool  go run main.go
Fibonacci: 1
Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55

逃逸如下:

~/go/src/gitHub/test/pool  go build -gcflags=-m
# gitHub/test/pool
./main.go:7:9: can inline Fibonacci.func1
./main.go:7:9: func literal escapes to heap
./main.go:7:9: func literal escapes to heap
./main.go:8:10: &b escapes to heap
./main.go:6:5: moved to heap: b
./main.go:8:13: &a escapes to heap
./main.go:6:2: moved to heap: a
./main.go:17:34: f() escapes to heap
./main.go:17:13: main ... argument does not escape

Fibonacci()函數(shù)中原本屬于局部變量的a和b由于閉包的引用,不得不將二者放到堆上,以致產(chǎn)生逃逸。

逃逸分析的作用是什么呢?

  • 逃逸分析的好處是為了減少gc的壓力,不逃逸的對(duì)象分配在棧上,當(dāng)函數(shù)返回時(shí)就回收了資源,不需要gc標(biāo)記清除。
  • 逃逸分析完后可以確定哪些變量可以分配在棧上,棧的分配比堆快,性能好(逃逸的局部變量會(huì)在堆上分配 ,而沒有發(fā)生逃逸的則有編譯器在棧上分配)。
  • 同步消除,如果你定義的對(duì)象的方法上有同步鎖,但在運(yùn)行時(shí),卻只有一個(gè)線程在訪問,此時(shí)逃逸分析后的機(jī)器碼,會(huì)去掉同步鎖運(yùn)行。

逃逸總結(jié):

  • 棧上分配內(nèi)存比在堆中分配內(nèi)存有更高的效率
  • 棧上分配的內(nèi)存不需要GC處理
  • 堆上分配的內(nèi)存使用完畢會(huì)交給GC處理
  • 逃逸分析目的是決定內(nèi)分配地址是棧還是堆
  • 逃逸分析在編譯階段完成

提問:函數(shù)傳遞指針真的比傳值效率高嗎?

我們知道傳遞指針可以減少底層值的拷貝,可以提高效率,但是如果拷貝的數(shù)據(jù)量小,由于指針傳遞會(huì)產(chǎn)生逃逸,可能會(huì)使用堆,也可能會(huì)增加GC的負(fù)擔(dān),所以傳遞指針不一定是高效的。

在官網(wǎng) (golang.org) FAQ 上有一個(gè)關(guān)于變量分配的問題如下:

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.

翻譯如下:

如何得知變量是分配在棧(stack)上還是堆(heap)上?

準(zhǔn)確地說,你并不需要知道。Golang 中的變量只要被引用就一直會(huì)存活,存儲(chǔ)在堆上還是棧上由內(nèi)部實(shí)現(xiàn)決定而和具體的語(yǔ)法沒有關(guān)系。

知道變量的存儲(chǔ)位置確實(shí)和效率編程有關(guān)系。如果可能,Golang 編譯器會(huì)將函數(shù)的局部變量分配到函數(shù)棧幀(stack frame)上。 然而,如果編譯器不能確保變量在函數(shù) return之后不再被引用,編譯器就會(huì)將變量分配到堆上。而且,如果一個(gè)局部變量非常大,那么它也應(yīng)該被分配到堆上而不是棧上。

當(dāng)前情況下,如果一個(gè)變量被取地址,那么它就有可能被分配到堆上。然而,還要對(duì)這些變量做逃逸分析,如果函數(shù)return之后,變量不再被引用,則將其分配到棧上。

二. golang 臨時(shí)對(duì)象池sync.Pool

1. 內(nèi)存碎片化問題

實(shí)際項(xiàng)目基本都是通過

c := make([]int, 0, l)

來申請(qǐng)內(nèi)存,長(zhǎng)度都是不確定的,自然而然這些變量都會(huì)申請(qǐng)到堆上面了。

Golang使用的垃圾回收算法是『標(biāo)記——清除』。

簡(jiǎn)單得說,就是程序要從操作系統(tǒng)申請(qǐng)一塊比較大的內(nèi)存,內(nèi)存分成小塊,通過鏈表鏈接。

每次程序申請(qǐng)內(nèi)存,就從鏈表上面遍歷每一小塊,找到符合的就返回其地址,沒有合適的就從操作系統(tǒng)再申請(qǐng)。如果申請(qǐng)內(nèi)存次數(shù)較多,而且申請(qǐng)的大小不固定,就會(huì)引起內(nèi)存碎片化的問題。

申請(qǐng)的堆內(nèi)存并沒有用完,但是用戶申請(qǐng)的內(nèi)存的時(shí)候卻沒有合適的空間提供。這樣會(huì)遍歷整個(gè)鏈表,還會(huì)繼續(xù)向操作系統(tǒng)申請(qǐng)內(nèi)存。這就能解釋我一開始描述的問題,申請(qǐng)一塊內(nèi)存變成了慢語(yǔ)句。

到此這篇關(guān)于一文帶你搞懂golang中內(nèi)存分配逃逸分析的文章就介紹到這了,更多相關(guān)go內(nèi)存分配逃逸內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang并發(fā)控制之errgroup使用詳解

    Golang并發(fā)控制之errgroup使用詳解

    errgroup?是?Go?官方庫(kù)?x?中提供的一個(gè)非常實(shí)用的工具,用于并發(fā)執(zhí)行多個(gè)?goroutine,并且方便的處理錯(cuò)誤,下面就跟隨小編一起來了解下的它的具體使用吧
    2024-11-11
  • 重學(xué)Go語(yǔ)言之文件操作詳解

    重學(xué)Go語(yǔ)言之文件操作詳解

    有很多場(chǎng)景都需要對(duì)文件進(jìn)行讀取或者寫入,比如讀取配置文件或者寫入日志文件,在Go語(yǔ)言中,操作文件應(yīng)該算是一件比較簡(jiǎn)單的事情,我們?cè)谶@一篇文章中,一起來探究一下
    2023-08-08
  • Go語(yǔ)言接口與多態(tài)詳細(xì)介紹

    Go語(yǔ)言接口與多態(tài)詳細(xì)介紹

    Go語(yǔ)言的接口類型是一組方法定義的集合,它體現(xiàn)了多態(tài)性、高內(nèi)聚和低耦合的設(shè)計(jì)思想,接口通過interface關(guān)鍵字定義,無(wú)需實(shí)現(xiàn)具體方法,任何實(shí)現(xiàn)了接口所有方法的類型即視為實(shí)現(xiàn)了該接口,感興趣的朋友一起看看吧
    2024-09-09
  • Go語(yǔ)言利用heap實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列

    Go語(yǔ)言利用heap實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中heap的使用以及如何利用heap實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列的相關(guān)資料,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-05-05
  • Go每日一庫(kù)之zap日志庫(kù)的安裝使用指南

    Go每日一庫(kù)之zap日志庫(kù)的安裝使用指南

    這篇文章主要為大家介紹了Go每日一庫(kù)之zap安裝使用示例學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Golang操作excel的方法

    Golang操作excel的方法

    這篇文章主要介紹了Golang操作excel的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    這篇文章主要為大家介紹了Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • GO如何模擬流操作實(shí)現(xiàn)示例探究

    GO如何模擬流操作實(shí)現(xiàn)示例探究

    這篇文章主要為大家介紹了GO如何模擬流操作實(shí)現(xiàn)示例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Golang編程實(shí)現(xiàn)生成n個(gè)從a到b不重復(fù)隨機(jī)數(shù)的方法

    Golang編程實(shí)現(xiàn)生成n個(gè)從a到b不重復(fù)隨機(jī)數(shù)的方法

    這篇文章主要介紹了Golang編程實(shí)現(xiàn)生成n個(gè)從a到b不重復(fù)隨機(jī)數(shù)的方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言字符串操作及隨機(jī)數(shù)生成的相關(guān)操作技巧,需要的朋友可以參考下
    2017-01-01
  • golang實(shí)現(xiàn)簡(jiǎn)單rpc調(diào)用過程解析

    golang實(shí)現(xiàn)簡(jiǎn)單rpc調(diào)用過程解析

    這篇文章主要介紹了golang實(shí)現(xiàn)簡(jiǎn)單rpc調(diào)用,包括RPC具體實(shí)現(xiàn)結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05

最新評(píng)論