go語言程序所占用內存 go 限制內存

Go 語言內存管理(三):逃逸分析

Go 語言較之 C 語言一個很大的優勢就是自帶 GC 功能,可 GC 并不是沒有代價的。寫 C 語言的時候,在一個函數內聲明的變量,在函數退出后會自動釋放掉,因為這些變量分配在棧上。如果你期望變量的數據可以在函數退出后仍然能被訪問,就需要調用 malloc 方法在堆上申請內存,如果程序不再需要這塊內存了,再調用 free 方法釋放掉。Go 語言不需要你主動調用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變量,使用堆內存。編譯器的這個分析過程就叫做逃逸分析。

射洪ssl適用于網站、小程序/APP、API接口等需要進行數據傳輸應用場景,ssl證書未來市場廣闊!成為創新互聯的ssl證書銷售渠道,可以享受市場價格4-6折優惠!如果有意向歡迎電話聯系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!

所以你在一個函數中通過 dict := make(map[string]int) 創建一個 map 變量,其背后的數據是放在棧空間上還是堆空間上,是不一定的。這要看編譯器分析的結果。

可逃逸分析并不是百分百準確的,它有缺陷。有的時候你會發現有些變量其實在棧空間上分配完全沒問題的,但編譯后程序還是把這些數據放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。

Go 語言雖然在內存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發,但如果你要在性能上較真的話,還是要掌握這些基礎知識。

這里不對堆內存和棧內存的區別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數的結束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:

這里舉一個小例子,來對比下堆棧的差別:

stack 函數中的變量 i 在函數退出會自動釋放;而 heap 函數返回的是對變量 i 的引用,也就是說 heap() 退出后,表示變量 i 還要能被訪問,它會自動被分配到堆空間上。

他們編譯出來的代碼如下:

邏輯的復雜度不言而喻,從上面的匯編中可看到, heap() 函數調用了 runtime.newobject() 方法,它會調用 mallocgc 方法從 mcache 上申請內存,申請的內部邏輯前面文章已經講述過。堆內存分配不僅分配上邏輯比??臻g分配復雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計算資源對其進行標記回收(也就是 GC 成本)。

Go 編輯器會自動幫我們找出需要進行動態分配的變量,它是在編譯時追蹤一個變量的生命周期,如果能確認一個數據只在函數空間內訪問,不會被外部使用,則使用??臻g,否則就要使用堆空間。

我們在 go build 編譯代碼時,可使用 -gcflags '-m' 參數來查看逃逸分析日志。

以上面的兩個函數為例,編譯的日志輸出是:

日志中的 i escapes to heap 表示該變量數據逃逸到了堆上。

需要使用堆空間,所以逃逸,這沒什么可爭議的。但編譯器有時會將 不需要 使用堆空間的變量,也逃逸掉。這里是容易出現性能問題的大坑。網上有很多相關文章,列舉了一些導致逃逸情況,其實總結起來就一句話:

多級間接賦值容易導致逃逸 。

這里的多級間接指的是,對某個引用類對象中的引用類成員進行賦值。Go 語言中的引用類數據類型有 func , interface , slice , map , chan , *Type(指針) 。

記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數據類型,則會導致 Value 逃逸。這里的等號 = 不單單只賦值,也表示參數傳遞。

根據公式,我們假設一個變量 data 是以下幾種類型,相應的可以得出結論:

下面給出一些實際的例子:

如果變量值是一個函數,函數的參數又是引用類型,則傳遞給它的參數都會逃逸。

上例中 te 的類型是 func(*int) ,屬于引用類型,參數 *int 也是引用類型,則調用 te(j) 形成了為 te 的參數(成員) *int 賦值的現象,即 te.i = j 會導致逃逸。代碼中其他幾種調用都沒有形成 多級間接賦值 情況。

同理,如果函數的參數類型是 slice , map 或 interface{} 都會導致參數逃逸。

匿名函數的調用也是一樣的,它本質上也是一個函數變量。有興趣的可以自己測試一下。

只要使用了 Interface 類型(不是 interafce{} ),那么賦值給它的變量一定會逃逸。因為 interfaceVariable.Method() 先是間接的定位到它的實際值,再調用實際值的同名方法,執行時實際值作為參數傳遞給方法。相當于 interfaceVariable.Method.this = realValue

向 channel 中發送數據,本質上就是為 channel 內部的成員賦值,就像給一個 slice 中的某一項賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導致發送到 channel 中的數據逃逸。

這本來也是情理之中的,發送給 channel 的數據是要與其他函數分享的,為了保證發送過去的指針依然可用,只能使用堆分配。

可變參數如 func(arg ...string) 實際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數逃逸的原因。

例子非常多,這里不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導致數據逃逸。這里加上 容易 二字是因為隨著語言的發展,相信這些問題會被慢慢解決,但現階段,這個可以作為我們分析逃逸現象的依據。

下面代碼中包含 2 種很常規的寫法,但他們卻有著很大的性能差距,建議自己想下為什么。

Benchmark 和 pprof 給出的結果:

熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,并進行優化。

多級間接賦值會導致 Go 編譯器出現不必要的逃逸,在一些情況下可能我們只需要修改一下數據結構就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因為它會增加一級訪問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。

大多數情況下,性能優化都會為程序帶來一定的復雜度。建議實際項目中還是怎么方便怎么寫,功能完成后通過性能分析找到瓶頸所在,再對局部進行優化。

Golang 1.14中內存分配、清掃和內存回收

Golang的內存分配是由golang runtime完成,其內存分配方案借鑒自tcmalloc。

主要特點就是

本文中的element指一定大小的內存塊是內存分配的概念,并為出現在golang runtime源碼中

本文講述x8664架構下的內存分配

Golang 內存分配有下面幾個主要結構

Tiny對象是指內存尺寸小于16B的對象,這類對象的分配使用mcache的tiny區域進行分配。當tiny區域空間耗盡時刻,它會從mcache.alloc[tinySpanClass]指向的mspan中找到空閑的區域。當然如果mcache中span空間也耗盡,它會觸發從mcentral補充mspan到mcache的流程。

小對象是指對象尺寸在(16B,32KB]之間的對象,這類對象的分配原則是:

1、首先根據對象尺寸將對象歸為某個SpanClass上,這個SpanClass上所有的element都是一個統一的尺寸。

2、從mcache.alloc[SpanClass]找到mspan,看看有無空閑的element,如果有分配成功。如果沒有繼續。

3、從mcentral.allocSpan[SpanClass]的nonempty和emtpy中找到合適的mspan,返回給mcache。如果沒有找到就進入mcentral.grow()—mheap.alloc()分配新的mspan給mcentral。

大對象指尺寸超出32KB的對象,此時直接從mheap中分配,不會走mcache和mcentral,直接走mheap.alloc()分配一個SpanClass==0 的mspan表示這部分分配空間。

對于程序分配常用的tiny和小對象的分配,可以通過無鎖的mcache提升分配性能。mcache不足時刻會拿mcentral的鎖,然后從mcentral中充mspan 給mcache。大對象直接從mheap 中分配。

在x8664環境上,golang管理的有效的程序虛擬地址空間實質上只有48位。在mheap中有一個pages pageAlloc成員用于管理golang堆內存的地址空間。golang從os中申請地址空間給自己管理,地址空間申請下來以后,golang會將地址空間根據實際使用情況標記為free或者alloc。如果地址空間被分配給mspan或大對象后,那么被標記為alloc,反之就是free。

Golang認為地址空間有以下4種狀態:

Golang同時定義了下面幾個地址空間操作函數:

在mheap結構中,有一個名為pages成員,它用于golang 堆使用虛擬地址空間進行管理。其類型為pageAlloc

pageAlloc 結構表示的golang 堆的所有地址空間。其中最重要的成員有兩個:

在golang的gc流程中會將未使用的對象標記為未使用,但是這些對象所使用的地址空間并未交還給os。地址空間的申請和釋放都是以golang的page為單位(實際以chunk為單位)進行的。sweep的最終結果只是將某個地址空間標記可被分配,并未真正釋放地址空間給os,真正釋放是后文的scavenge過程。

在gc mark結束以后會使用sweep()去嘗試free一個span;在mheap.alloc 申請mspan時刻,也使用sweep去清掃一下。

清掃mspan主要涉及到下面函數

如上節所述,sweep只是將page標記為可分配,但是并未把地址空間釋放;真正的地址空間釋放是scavenge過程。

真正的scavenge是由pageAlloc.scavenge()—sysUnused()將掃描到待釋放的chunk所表示的地址空間釋放掉(使用sysUnused()將地址空間還給os)

golang的scavenge過程有兩種:

嵌入式golang占用內存高

嵌入式golang占用內存高可能問題在于緩存。

清空日志后比較驚喜地發現,內存瞬間暴降至20M。

嵌入式系統由硬件和軟件組成.是能夠獨立進行運作的器件。其軟件內容只包括軟件運行環境及其操作系統。硬件內容包括信號處理器、存儲器、通信模塊等在內的多方面的內容。相比于一般的計算機處理系統而言,嵌入式系統存在較大的差異性,它不能實現大容量的存儲功能,因為沒有與之相匹配的大容量介質,大部分采用的存儲介質有E-PROM、EEPROM等,軟件部分以API編程接口作為開發平臺的核心。嵌入式系統最核心的層次是中央處理單元部分,它包含運算器和控制器模塊,在cpu的基礎上進一步配上存儲器模塊、電源模塊、復位模塊等就構成了通常所說的最小系統。由于技術的進步,集成電路生產商通常會把許多外設做進同一個集成電路中,這樣在使用上更加方便,這樣一個芯片通常稱之為微控制器。在微控制器的基礎上進一步擴展電源傳感與檢測、執行器模塊以及配套軟件并構成一個具有特定功能的完整單元,就稱之為一個嵌入式系統或嵌入式應用。

(十一)golang 內存分析

編寫過C語言程序的肯定知道通過malloc()方法動態申請內存,其中內存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業界比較出名的內存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內存碎片和性能上均比glic有比較大的優勢,在多線程環境中效果更明顯。

Golang中也實現了內存分配器,原理與tcmalloc類似,簡單的說就是維護一塊大的全局內存,每個線程(Golang中為P)維護一塊小的私有內存,私有內存不足再從全局申請。另外,內存分配與GC(垃圾回收)關系密切,所以了解GC前有必要了解內存分配的原理。

為了方便自主管理內存,做法便是先向系統申請一塊內存,然后將內存切割成小塊,通過一定的內存分配算法管理內存。 以64位系統為例,Golang程序啟動時會向系統申請的內存如下圖所示:

預申請的內存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區,應用中需要的內存從這里分配。其中spans和bitmap是為了管理arena區而存在的。

arena的大小為512G,為了方便管理把arena區域劃分成一個個的page,每個page為8KB,一共有512GB/8KB個頁;

spans區域存放span的指針,每個指針對應一個page,所以span區域的大小為(512GB/8KB)乘以指針大小8byte = 512M

bitmap區域大小也是通過arena計算出來,不過主要用于GC。

span是用于管理arena頁的關鍵數據結構,每個span中包含1個或多個連續頁,為了滿足小對象分配,span中的一頁會劃分更小的粒度,而對于大對象比如超過頁大小,則通過多頁實現。

根據對象大小,劃分了一系列class,每個class都代表一個固定大小的對象,以及每個span的大小。如下表所示:

上表中每列含義如下:

class: class ID,每個span結構中都有一個class ID, 表示該span可處理的對象類型

bytes/obj:該class代表對象的字節數

bytes/span:每個span占用堆的字節數,也即頁數乘以頁大小

objects: 每個span可分配的對象個數,也即(bytes/spans)/(bytes/obj)waste

bytes: 每個span產生的內存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。

span是內存管理的基本單位,每個span用于管理特定的class對象, 跟據對象大小,span將一個或多個頁拆分成多個塊進行管理。src/runtime/mheap.go:mspan定義了其數據結構:

以class 10為例,span和管理的內存如下圖所示:

spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時就指定了某個頁的地址。allocBits指向一個位圖,每位代表一個塊是否被分配,本例中有兩個塊已經被分配,其allocCount也為2。next和prev用于將多個span鏈接起來,這有利于管理多個span,接下來會進行說明。

有了管理內存的基本單位span,還要有個數據結構來管理span,這個數據結構叫mcentral,各線程需要內存時從mcentral管理的span中申請內存,為了避免多線程申請內存時不斷的加鎖,Golang為每個線程分配了span的緩存,這個緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數據結構

alloc為mspan的指針數組,數組大小為class總數的2倍。數組中每個元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對象中包含了指針,第二組列表中所表示的對象不含有指針,這么做是為了提高GC掃描性能,對于不包含指針的span列表,沒必要去掃描。根據對象是否包含指針,將對象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進行掃描。mcache和span的對應關系如下圖所示:

mchache在初始化時是沒有任何span的,在使用過程中會動態的從central中獲取并緩存下來,跟據使用情況,每種class的span個數也不相同。上圖所示,class 0的span數比class1的要多,說明本線程中分配的小對象要多一些。

cache作為線程的私有資源為單個線程服務,而central則是全局資源,為多個線程服務,當某個線程內存不足時會向central申請,當某個線程釋放內存時又會回收進central。src/runtime/mcentral.go:mcentral定義了central數據結構:

lock: 線程間互斥鎖,防止多線程讀寫沖突

spanclass : 每個mcentral管理著一組有相同class的span列表

nonempty: 指還有內存可用的span列表

empty: 指沒有內存可用的span列表

nmalloc: 指累計分配的對象個數線程從central獲取span步驟如下:

將span歸還步驟如下:

從mcentral數據結構可見,每個mcentral對象只管理特定的class規格的span。事實上每種class都會對應一個mcentral,這個mcentral的集合存放于mheap數據結構中。src/runtime/mheap.go:mheap定義了heap的數據結構:

lock: 互斥鎖

spans: 指向spans區域,用于映射span和page的關系

bitmap:bitmap的起始地址

arena_start: arena區域首地址

arena_used: 當前arena已使用區域的最大地址

central: 每種class對應的兩個mcentral

從數據結構可見,mheap管理著全部的內存,事實上Golang就是通過一個mheap類型的全局變量進行內存管理的。mheap內存管理示意圖如下:

系統預分配的內存分為spans、bitmap、arean三個區域,通過mheap管理起來。接下來看內存分配過程。

針對待分配對象的大小不同有不同的分配邏輯:

(0, 16B) 且不包含指針的對象: Tiny分配

(0, 16B) 包含指針的對象:正常分配

[16B, 32KB] : 正常分配

(32KB, -) : 大對象分配其中Tiny分配和大對象分配都屬于內存管理的優化范疇,這里暫時僅關注一般的分配方法。

以申請size為n的內存為例,分配步驟如下:

Golang內存分配是個相當復雜的過程,其中還摻雜了GC的處理,這里僅僅對其關鍵數據結構進行了說明,了解其原理而又不至于深陷實現細節。1、Golang程序啟動時申請一大塊內存并劃分成spans、bitmap、arena區域

2、arena區域按頁劃分成一個個小塊。

3、span管理一個或多個頁。

4、mcentral管理多個span供線程申請使用

5、mcache作為線程私有資源,資源來源于mcentral。

網頁標題:go語言程序所占用內存 go 限制內存
URL網址:http://m.kartarina.com/article34/hgjhse.html

成都網站建設公司_創新互聯,為您提供響應式網站、手機網站建設、網站設計、軟件開發品牌網站設計網站收錄

廣告

聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯

手機網站建設
主站蜘蛛池模板: 日韩人妻无码精品一专区| 亚洲AV无码专区在线亚| 高清无码v视频日本www| 无码粉嫩虎白一线天在线观看| 无码Aⅴ在线观看| 13小箩利洗澡无码视频网站免费| 少妇无码一区二区三区免费| 国产精品无码亚洲一区二区三区| 色窝窝无码一区二区三区成人网站| 一本大道无码日韩精品影视| 中文字幕无码精品亚洲资源网久久| 日韩a级无码免费视频| 岛国av无码免费无禁网站| 中文有码无码人妻在线| 老司机亚洲精品影院无码| 国产无码一区二区在线| 无码国产精品一区二区免费式直播 | 无码人妻精品一区二区三区蜜桃 | 久久午夜无码鲁丝片| 韩国19禁无遮挡啪啪无码网站| 91精品久久久久久无码| 亚洲av中文无码乱人伦在线r▽| 日韩精品无码免费视频| 无码人妻精品一二三区免费| 伊人久久精品无码麻豆一区| 色情无码WWW视频无码区小黄鸭| 永久免费av无码入口国语片| 天码av无码一区二区三区四区| 日韩毛片免费无码无毒视频观看| 高清无码中文字幕在线观看视频| 无码人妻少妇久久中文字幕蜜桃| 亚洲AV永久无码精品水牛影视| 2014AV天堂无码一区| 亚洲va无码手机在线电影| 亚洲av中文无码乱人伦在线咪咕| 亚洲av永久无码精品古装片| 一区二区三区人妻无码| 性无码专区无码片| 91精品日韩人妻无码久久不卡| 无码夫の前で人妻を侵犯| 精品人妻无码一区二区三区蜜桃一|