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

Go逃逸分析示例詳解

 更新時(shí)間:2022年08月16日 08:56:23   作者:王中陽(yáng)Go  
這篇文章主要為大家介紹了Go逃逸分析示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言大綱

這個(gè)月我會(huì)整理分享一系列后端工程師求職面試相關(guān)的文章,知識(shí)脈絡(luò)圖如下:

  • JAVA/GO/PHP 面試常問(wèn)的知識(shí)點(diǎn)
  • DB:MySql PgSql
  • Cache: Redis MemCache MongoDB
  • 數(shù)據(jù)結(jié)構(gòu)
  • 算法
  • 微服務(wù)&高并發(fā)
  • 流媒體
  • WEB3.0
  • 源碼分析

通過(guò)這一系列的文章,大家不僅能復(fù)習(xí)梳理后端開(kāi)發(fā)相關(guān)的知識(shí)點(diǎn),也可以了解目前的市場(chǎng)環(huán)境對(duì)服務(wù)端開(kāi)發(fā),尤其是對(duì)Go開(kāi)發(fā)工程師的崗位要求,需要掌握哪些核心技術(shù)。

上一篇文章: 【狂刷面試題】GO常見(jiàn)面試題匯總我們介紹了:切片相關(guān)的知識(shí)點(diǎn);深拷貝和淺拷貝的區(qū)別;new和make的區(qū)別;map的底層實(shí)現(xiàn)是hash,默認(rèn)不支持排序,我們可以通過(guò)什么思路來(lái)實(shí)現(xiàn)map有序取值;值類(lèi)型和引用類(lèi)型的區(qū)別;GO語(yǔ)言中堆和棧的區(qū)別,什么數(shù)據(jù)會(huì)分配到堆中,什么變量會(huì)分配到棧中;

感興趣的同學(xué)可以先看上一篇文章,能更好的理解這篇介紹的硬核知識(shí)點(diǎn):逃逸分析。

逃逸分析

我們?cè)谥坝刑岬蕉押蜅5母拍?,要搞清楚GO的逃逸分析一定要先搞清楚堆棧的特點(diǎn):

正如我們上面提到的,內(nèi)存分配既可以分配到堆中,也可以分配到棧中。

那么什么樣的數(shù)據(jù)會(huì)被分配到棧中,什么樣的數(shù)據(jù)又會(huì)被分配到堆中呢?GO語(yǔ)言是如何進(jìn)行內(nèi)存分配的呢?其設(shè)計(jì)初衷和實(shí)現(xiàn)原理是什么呢?

我們先來(lái)了解一下內(nèi)存管理、堆、棧的知識(shí)點(diǎn):

內(nèi)存管理

內(nèi)存管理主要包括兩個(gè)動(dòng)作:分配與釋放。逃逸分析就是服務(wù)于內(nèi)存分配,為了更好理解逃逸分析,我們?cè)賮?lái)回顧一下堆棧的特點(diǎn):

在Go中,棧的內(nèi)存是由編譯器自動(dòng)進(jìn)行分配和釋放,棧區(qū)往往存儲(chǔ)著函數(shù)參數(shù)、局部變量和調(diào)用函數(shù)幀,它們隨著函數(shù)的創(chuàng)建而分配,函數(shù)的退出而銷(xiāo)毀。

一個(gè)goroutine對(duì)應(yīng)一個(gè)棧,棧是調(diào)用棧(call stack)的簡(jiǎn)稱(chēng)。一個(gè)棧通常又包含了許多棧幀(stack frame),它描述的是函數(shù)之間的調(diào)用關(guān)系,每一幀對(duì)應(yīng)一個(gè)尚未返回的函數(shù)調(diào)用,它本身也是以棧形式存放數(shù)據(jù)。

與棧不同的是,應(yīng)用程序在運(yùn)行時(shí)只會(huì)存在一個(gè)堆。

我們可以簡(jiǎn)單理解為:我們?cè)贕O開(kāi)發(fā)過(guò)程中要考慮的內(nèi)存管理只是針對(duì)堆內(nèi)存而言的。

程序在運(yùn)行期間可以主動(dòng)從堆上申請(qǐng)內(nèi)存,這些內(nèi)存通過(guò)Go的內(nèi)存分配器分配,并由垃圾收集器回收。

堆和棧的對(duì)比

加鎖

  • 棧不需要加鎖:棧是每個(gè)goroutine獨(dú)有的,這就意味著棧上的內(nèi)存操作是不需要加鎖的。

  • 堆有時(shí)需要加鎖:堆上的內(nèi)存,有時(shí)需要加鎖防止多線程沖突

延伸知識(shí)點(diǎn):為什么堆上的內(nèi)存有時(shí)需要加鎖?而不是一直需要加鎖呢?

因?yàn)镚o的內(nèi)存分配策略學(xué)習(xí)了TCMalloc的線程緩存思想,他為每個(gè)處理器P分配了一個(gè)mcache,從mcache分配內(nèi)存也是無(wú)鎖的

性能

  • 堆內(nèi)存管理 性能差:對(duì)于程序堆上的內(nèi)存回收,還需要通過(guò)標(biāo)記清除階段,例如Go采用的三色標(biāo)記法。
  • 棧內(nèi)存管理 性能好:棧上的內(nèi)存,它的分配與釋放非常高效的。簡(jiǎn)單地說(shuō),它只需要兩個(gè)CPU指令:一個(gè)是分配入棧,另外一個(gè)是棧內(nèi)釋放。只需要借助于棧相關(guān)寄存器即可完成。

緩存策略

  • 棧緩存性能更好
  • 堆緩存性能較差

原因是:棧內(nèi)存能更好地利用CPU的緩存策略,因?yàn)闂?臻g相較于堆來(lái)說(shuō)是更連續(xù)的。

逃逸分析優(yōu)勢(shì)

上面說(shuō)了這么多堆和棧的知識(shí)點(diǎn),目的是為了讓大家更好的理解逃逸分析。

正如我們講的,相比于把內(nèi)存分配到堆中,分配到棧中優(yōu)勢(shì)更明顯。

Go語(yǔ)言也是這么做的:Go編譯器會(huì)盡可能將變量分配到到棧上。

但是,當(dāng)編譯器無(wú)法證明函數(shù)返回后,該變量沒(méi)有被引用,那么編譯器就必須在堆上分配該變量,以此避免懸掛指針(dangling pointer)。另外,如果局部變量非常大,也會(huì)將其分配在堆上。

Go是如何確定內(nèi)存是分配到棧上還是堆上的呢?

答案就是:逃逸分析。

編譯器通過(guò)逃逸分析技術(shù)去選擇堆或者棧,逃逸分析的基本思想如下:檢查變量的生命周期是否是完全可知的,如果通過(guò)檢查,則在棧上分配。否則,就是所謂的逃逸,必須在堆上進(jìn)行分配。

逃逸分析原則

Go語(yǔ)言雖然沒(méi)有明確說(shuō)明逃逸分析原則,但是有以下幾點(diǎn)準(zhǔn)則,是可以參考的。

  • 不同于jvm的運(yùn)行時(shí)逃逸分析,Go的逃逸分析是在編譯期完成的:編譯期無(wú)法確定的參數(shù)類(lèi)型必定放到堆中;
  • 如果變量在函數(shù)外部沒(méi)有引用,則優(yōu)先放到棧中;
  • 如果變量在函數(shù)外部存在引用,則必定放在堆中;
  • 如果變量占用內(nèi)存較大時(shí),則優(yōu)先放到堆中;

逃逸分析舉例

我們使用這個(gè)命令來(lái)查看逃逸分析的結(jié)果: go build -gcflags '-m -m -l'

1.參數(shù)是interface類(lèi)型

package main
import "fmt"
func main() {
   a := 666
   fmt.Println(a)
}

運(yùn)行結(jié)果

原因分析

因?yàn)镻rintln(a ...interface{})的參數(shù)是interface{}類(lèi)型,編譯期無(wú)法確定其具體的參數(shù)類(lèi)型,所以?xún)?nèi)存分配到堆中。

2. 變量在函數(shù)外部有引用

package main
func test() *int {
   a := 10
   return &a
}
func main() {
   _ = test()
}

運(yùn)行結(jié)果

原因分析

變量a在函數(shù)外部存在引用。

我們來(lái)分析一下執(zhí)行過(guò)程:當(dāng)函數(shù)執(zhí)行完畢,對(duì)應(yīng)的棧幀就被銷(xiāo)毀,但是引用已經(jīng)被返回到函數(shù)之外。如果這時(shí)外部通過(guò)引用地址取值,雖然地址還在,但是這塊內(nèi)存已經(jīng)被釋放回收了,這就是非法內(nèi)存。

在這種情況下必須分配到堆上。

3. 變量?jī)?nèi)存占用較大

package main
func test() {
   a := make([]int, 10000, 10000)
   for i := 0; i < 10000; i++ {
      a[i] = i
   }
}
func main() {
   test()
}

運(yùn)行結(jié)果

原因分析

我們定義了一個(gè)容量為10000的int類(lèi)型切片,內(nèi)存分配到了棧上。

我們?cè)俸?jiǎn)單修改一下代碼,將切片的容量和長(zhǎng)度修改為1,再次查看逃逸分析的結(jié)果,我們發(fā)現(xiàn),沒(méi)有發(fā)生逃逸,內(nèi)存默認(rèn)分類(lèi)到了棧上。

所以,當(dāng)變量占用內(nèi)存較大時(shí),會(huì)發(fā)生逃逸分析,將內(nèi)存分配到堆上。

4. 變量大小不確定時(shí)

我們?cè)俸?jiǎn)單修改一下上面的代碼:

package main
func test() {
   l := 1
   a := make([]int, l, l)
   for i := 0; i < l; i++ {
      a[i] = i
   }
}
func main() {
   test()
}

運(yùn)行結(jié)果

原因分析

我們通過(guò)控制臺(tái)的輸出結(jié)果可以很明顯的看出:發(fā)生了逃逸,分配到了heap堆中。

原因是這樣的:

我們雖然在代碼段中給變量 l 賦值了1,但是編譯期間只能識(shí)別到初始化int類(lèi)型切片時(shí),傳入的長(zhǎng)度和容量是變量l,編譯器并不能確定變量l的值,所以發(fā)生了逃逸,會(huì)把內(nèi)存分配到堆中。

思考題

好了,我們舉了4個(gè)逃逸分析的經(jīng)典案例,相信聰明的你已經(jīng)理解了逃逸分析的作用和發(fā)生逃逸的場(chǎng)景。

我們來(lái)想一下,在理解逃逸分析的原理之后,在開(kāi)發(fā)的過(guò)程中如何更好的編碼,進(jìn)而提高程序的效率,更好的利用內(nèi)存呢?

如何實(shí)踐?

理解逃逸分析一定能幫助我們寫(xiě)出更好的程序。知道變量分配在棧堆之上的差別后,我們就要盡量寫(xiě)出分配在棧上的代碼。因?yàn)槎焉系淖兞孔兩俸?,可以減輕內(nèi)存分配的開(kāi)銷(xiāo),減小GC的壓力,提高程序的運(yùn)行速度。

但是我們也要有過(guò)猶不及的指導(dǎo)思想。

我認(rèn)為沒(méi)有一成不變的開(kāi)發(fā)模式,我們一定是在不斷的需求變化,業(yè)務(wù)變化中求得平衡的:

舉個(gè)日常開(kāi)發(fā)中函數(shù)傳參栗子:

有些場(chǎng)景下我們不應(yīng)該傳遞結(jié)構(gòu)體指針,而應(yīng)該直接傳遞結(jié)構(gòu)體。

為什么會(huì)這樣呢?雖然直接傳遞結(jié)構(gòu)體需要值拷貝,但是這是在棧上完成的操作,開(kāi)銷(xiāo)遠(yuǎn)比變量逃逸后動(dòng)態(tài)地在堆上分配內(nèi)存少的多。

當(dāng)然這種做法不是絕對(duì)的,要根據(jù)場(chǎng)景去分析:

  • 如果結(jié)構(gòu)體較大,傳遞結(jié)構(gòu)體指針更合適,因?yàn)橹羔橆?lèi)型相比值類(lèi)型能節(jié)省大量的內(nèi)存空間
  • 如果結(jié)構(gòu)體較小,傳遞結(jié)構(gòu)體更適合,因?yàn)樵跅I戏峙鋬?nèi)存,可以有效減少GC壓力

總結(jié)

通過(guò)本文的介紹,相信你一定加深了堆棧的理解;搞清楚逃逸分析的作用和原理之后能夠指導(dǎo)我們寫(xiě)出更優(yōu)雅的代碼。

我們?cè)谌粘i_(kāi)發(fā)中,要根據(jù)實(shí)際場(chǎng)景考慮,如何將內(nèi)存盡量分配到棧中,減少GC的壓力,提高性能。

如何找到應(yīng)用開(kāi)發(fā)效率,程序運(yùn)行效率,對(duì)機(jī)器的壓力及負(fù)載的平衡點(diǎn),是程序員進(jìn)階之旅中的必修課。

以上就是Go逃逸分析示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go逃逸分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 利用systemd部署golang項(xiàng)目的實(shí)現(xiàn)方法

    利用systemd部署golang項(xiàng)目的實(shí)現(xiàn)方法

    這篇文章主要介紹了利用systemd部署golang項(xiàng)目的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟

    Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟

    本文主要介紹了Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 一文詳解golang通過(guò)io包進(jìn)行文件讀寫(xiě)

    一文詳解golang通過(guò)io包進(jìn)行文件讀寫(xiě)

    這篇文章主要介紹了golang通過(guò)io包進(jìn)行文件讀寫(xiě)文中有詳細(xì)的代碼示例。對(duì)學(xué)習(xí)或工資有很好的幫助,需要的小伙伴可以參考閱讀一下
    2023-04-04
  • GO語(yǔ)言協(xié)程創(chuàng)建使用并通過(guò)channel解決資源競(jìng)爭(zhēng)

    GO語(yǔ)言協(xié)程創(chuàng)建使用并通過(guò)channel解決資源競(jìng)爭(zhēng)

    這篇文章主要為大家介紹了GO語(yǔ)言協(xié)程創(chuàng)建使用并通過(guò)channel解決資源競(jìng)爭(zhēng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 詳解Golang中的通道機(jī)制與應(yīng)用

    詳解Golang中的通道機(jī)制與應(yīng)用

    這篇文章主要來(lái)和大家一起深入探討了Go語(yǔ)言中通道(Channel)的各個(gè)方面,文章詳細(xì)解析了通道的類(lèi)型、操作方法以及垃圾回收機(jī)制,有需要的可以了解下
    2023-10-10
  • Go語(yǔ)言錯(cuò)誤處理異常捕獲+異常拋出

    Go語(yǔ)言錯(cuò)誤處理異常捕獲+異常拋出

    這篇文章主要介紹了Go語(yǔ)言錯(cuò)誤處理異常捕獲和異常拋出,Go語(yǔ)言的作者認(rèn)為java等語(yǔ)言的錯(cuò)誤處理底層實(shí)現(xiàn)較為復(fù)雜,就實(shí)現(xiàn)了函數(shù)可以返回錯(cuò)誤類(lèi)型以及簡(jiǎn)單的異常捕獲,雖然簡(jiǎn)單但是也非常精妙,大大的提高了運(yùn)行效率,下文需要的朋友可以參考一下
    2022-02-02
  • 一文搞懂Go?Exec?僵尸與孤兒進(jìn)程

    一文搞懂Go?Exec?僵尸與孤兒進(jìn)程

    本文主要介紹了Go?Exec?僵尸與孤兒進(jìn)程,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 手把手教你用VS?code快速搭建一個(gè)Golang項(xiàng)目

    手把手教你用VS?code快速搭建一個(gè)Golang項(xiàng)目

    Go語(yǔ)言是采用UTF8編碼的,理論上使用任何文本編輯器都能做Go語(yǔ)言開(kāi)發(fā),下面這篇文章主要給大家介紹了關(guān)于使用VS?code快速搭建一個(gè)Golang項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • go?build失敗報(bào)方法undefined的解決過(guò)程

    go?build失敗報(bào)方法undefined的解決過(guò)程

    go build命令用于編譯我們指定的源碼文件或代碼包以及它們的依賴(lài)包,下面這篇文章主要給大家介紹了關(guān)于go?build失敗報(bào)方法undefined的解決過(guò)程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • GO 語(yǔ)言學(xué)習(xí)指南

    GO 語(yǔ)言學(xué)習(xí)指南

    這篇文章主要介紹了GO 語(yǔ)言學(xué)習(xí)指南,介紹的相當(dāng)全面,有需要的小伙伴參考下吧。
    2015-01-01

最新評(píng)論