十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
本篇文章主要介紹golang的內(nèi)存分配,文中關(guān)于內(nèi)存分配的算法以及mcache的介紹均以實(shí)例展示,有需要的朋友可以參考一下。
程序內(nèi)存大致可以分為5個(gè)段text
、data
、bss
、stack
、heap
其中text
段用于程序指令、文字、靜態(tài)常量
data
與bss
段用于存儲(chǔ)全局變量
stack
段用于存儲(chǔ)函數(shù)調(diào)用與函數(shù)內(nèi)的變量,stack
段的數(shù)據(jù)可以被CPU快速訪問(wèn),stack
段的大小在運(yùn)行時(shí)是不能增加和減少的,銷(xiāo)毀只是通過(guò)棧指針的移動(dòng)來(lái)實(shí)現(xiàn)的。同時(shí),這也是為什么程序有時(shí)候會(huì)報(bào)錯(cuò)stack overflow的原因。
stack
段的內(nèi)存分配是編譯器實(shí)現(xiàn)的,我們無(wú)需關(guān)心。同時(shí)stack通常的大小是有限的。
因此對(duì)于大內(nèi)存的分配,或者想手動(dòng)創(chuàng)建或釋放內(nèi)存,就只能夠?qū)?code>heap段進(jìn)行操作,這就是俗稱(chēng)的動(dòng)態(tài)分配內(nèi)存。例如c語(yǔ)言中的malloc
、calloc
、free
以及C++中的new
、delete
內(nèi)存的分配屬于操作系統(tǒng)級(jí)別的操作、因此不管是cc++語(yǔ)言的分配,最后都需要調(diào)用操作系統(tǒng)的接口。以linux為例,malloc代碼可能調(diào)用了操作系統(tǒng)接口mmap
分配內(nèi)存
linux操作系統(tǒng)提供的內(nèi)存分配接口如下:
mmap/munmap 映射/釋放 指定大小的內(nèi)存.
brk/sbrk – 改變`data`段`結(jié)束的位置來(lái)擴(kuò)展heap段的內(nèi)存
madvise – 給操作系統(tǒng)建議如何管理內(nèi)存
set_thread_area/get_thread_area – 操作線程本地存儲(chǔ)空間
動(dòng)態(tài)內(nèi)存分配是操作系統(tǒng)為我們做的事情,其效率直接影響到運(yùn)行在操作系統(tǒng)上的程序。對(duì)于一般的程序來(lái)說(shuō),例如c語(yǔ)言中實(shí)現(xiàn)的malloc
,最后都是通過(guò)調(diào)用操作系統(tǒng)的接口來(lái)實(shí)現(xiàn)的。
動(dòng)態(tài)內(nèi)存的調(diào)度是一個(gè)艱難復(fù)雜的話題,其要實(shí)現(xiàn)的目標(biāo)包括:
快速分配和釋放
內(nèi)存開(kāi)銷(xiāo)小
使用所有內(nèi)存
避免碎片化
內(nèi)存分配的算法包括了:
K&R malloc
Region-based allocator
Buddy allocator
dlmalloc
slab allocator
同時(shí),由于算法解決的目標(biāo)等不同,還會(huì)有不同的變種,其他的目標(biāo)包括:
內(nèi)存開(kāi)銷(xiāo)小(例如buddy的元數(shù)據(jù)很大)
良好的內(nèi)存位置
cpu核心增加時(shí),擴(kuò)展性好
并發(fā)malloc / free
GO語(yǔ)言在進(jìn)行動(dòng)態(tài)內(nèi)存分配時(shí),實(shí)質(zhì)調(diào)用了上面的操作系統(tǒng)接口。由于Go語(yǔ)言并沒(méi)有調(diào)用c語(yǔ)言的malloc
等函數(shù)來(lái)分配,組織內(nèi)存,因此,其必須實(shí)現(xiàn)自己的內(nèi)存組織和調(diào)度方式。
GO語(yǔ)言借鑒了TCMalloc(Thread-Caching Malloc)的內(nèi)存分配方式
TCMalloc是一種內(nèi)存分配算法,比GNU C庫(kù)中的malloc要快2倍,正如其名字一樣,其是對(duì)于每一個(gè)線程構(gòu)建了緩存內(nèi)存。
TCMalloc解決了多線程時(shí)內(nèi)存分配的鎖競(jìng)爭(zhēng)問(wèn)題
TCMalloc對(duì)于小對(duì)象的分配非常高效
TCMalloc的核心思想是將內(nèi)存劃分為多個(gè)級(jí)別,以減少鎖的粒度。在TCMalloc內(nèi)部,內(nèi)存管理分為兩部分:小對(duì)象內(nèi)存(thread memory)和大對(duì)象內(nèi)存(page heap)。
小對(duì)象內(nèi)存管理將內(nèi)存頁(yè)分成多個(gè)固定大小的可分配的free列表。因此,每個(gè)線程都會(huì)有一個(gè)無(wú)鎖的小對(duì)象緩存,這使得在并行程序下分配小對(duì)象(<= 32k)非常有效。下圖的對(duì)象代表的是字節(jié)。
分配小對(duì)象時(shí)
我們將在相同大小的線程本地free list中查找,如果有,則從列表中刪除第一個(gè)對(duì)象并返回它
如果free list中為空,我們從中央free list中獲取對(duì)象(中央free list由所有線程共享),將它們放在線程本地free list中,并返回其中一個(gè)對(duì)象
如果中央free list也為空,將從中央頁(yè)分配器中分配內(nèi)存頁(yè)
,并將其分割為一組相同大小的對(duì)象,并將新對(duì)象放在中央free list中。和之前一樣,將其中一些對(duì)象移動(dòng)到線程本地空閑列表中
大對(duì)象內(nèi)存管理由頁(yè)
集合組成,將其稱(chēng)為頁(yè)堆(page heap)
當(dāng)分配的對(duì)象大于32K時(shí),將使用大對(duì)象分配方式。
第k個(gè)free list列表是包含k大小頁(yè)
的free list。第256個(gè)列表比較特殊,是長(zhǎng)度大于等于256頁(yè)的free list。
分配大對(duì)象時(shí),對(duì)于滿(mǎn)足k大小頁(yè)的分配
我們?cè)诘趉個(gè)free list中查找
如果該free list為空,則我們查找下一個(gè)更大的free list,依此類(lèi)推,最終,如有必要,我們將查找最后一個(gè)空閑列表。如果更大的free list符合條件,則會(huì)進(jìn)行內(nèi)存分割以符合當(dāng)前大小。
如果失敗,我們將從操作系統(tǒng)中獲取內(nèi)存。
內(nèi)存是通過(guò)連續(xù)頁(yè)
(稱(chēng)為Spans)的運(yùn)行來(lái)管理的(Go也根據(jù)Spans來(lái)管理內(nèi)存)
在TCMalloc中,span有兩種狀態(tài),已分配或是free狀態(tài)。如果為free,則span是位于頁(yè)堆列表中的一個(gè)。如果已分配,則它要么是已移交給應(yīng)用程序的大對(duì)象,要么是已分成多個(gè)小對(duì)象的序列。
go內(nèi)存分配器最初是基于TCMalloc的
Go allocator與TCMalloc類(lèi)似,內(nèi)存的管理由一系列頁(yè)
(spans/mspan對(duì)象)組成,使用(線程/協(xié)程)本地緩存并根據(jù)內(nèi)存大小進(jìn)行劃分。
在go語(yǔ)言中,Spans是8K或更大的連續(xù)內(nèi)存區(qū)域??梢栽?code>runtime/mheap.go中對(duì)應(yīng)的mspan結(jié)構(gòu)
type mspan struct { next *mspan // next span in list, or nil if none prev *mspan // previous span in list, or nil if none list *mSpanList // For debugging. TODO: Remove. startAddr uintptr // address of first byte of span aka s.base() npages uintptr // number of pages in span manualFreeList gclinkptr // list of free objects in mSpanManual spans freeindex uintptr nelems uintptr // number of object in the span. allocCache uint64 allocBits *gcBits gcmarkBits *gcBits sweepgen uint32 divMul uint16 // for divide by elemsize - divMagic.mul baseMask uint16 // if non-0, elemsize is a power of 2, & this will get object allocation base allocCount uint16 // number of allocated objects spanclass spanClass // size class and noscan (uint8) state mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods) needzero uint8 // needs to be zeroed before allocation divShift uint8 // for divide by elemsize - divMagic.shift divShift2 uint8 // for divide by elemsize - divMagic.shift2 elemsize uintptr // computed from sizeclass or from npages limit uintptr // end of data in span speciallock mutex // guards specials list specials *special // linked list of special records sorted by offset. }
如上圖,mspan是一個(gè)雙向鏈接列表對(duì)象,其中包含頁(yè)面的起始地址,它具有的頁(yè)的數(shù)量以及其大小。
mspan有三種類(lèi)型,分別是:
idle:沒(méi)有對(duì)象,可以釋放回操作系統(tǒng);或重新用于堆內(nèi)存;或重新用于棧內(nèi)存
in use:至少具有一個(gè)堆對(duì)象,并且可能有更多空間
stack:用于協(xié)程棧。可以存在于棧中,也可以存在于堆中,但不能同時(shí)存在于兩者中。
Go 像 TCMalloc 一樣為每一個(gè) 邏輯處理器(P)(Logical Processors) 提供一個(gè)本地線程緩存(Local Thread Cache)稱(chēng)作 mcache,所以如果 Goroutine 需要內(nèi)存可以直接從 mcache 中獲取,由于在同一時(shí)間只有一個(gè) Goroutine 運(yùn)行在 邏輯處理器(P)(Logical Processors) 上,所以中間不需要任何鎖的參與。mcache 包含所有大小規(guī)格的 mspan 作為緩存。
對(duì)于每一種大小規(guī)格都有兩個(gè)類(lèi)型:
scan -- 包含指針的對(duì)象。
noscan -- 不包含指針的對(duì)象。
采用這種方法的好處之一就是進(jìn)行垃圾回收時(shí) noscan 對(duì)象無(wú)需進(jìn)一步掃描是否引用其他活躍的對(duì)象。
mcentral是被所有邏輯處理器共享的
mcentral 對(duì)象收集所有給定規(guī)格大小的 span。每一個(gè) mcentral 都包含兩個(gè) mspan 的列表:
empty mspanList -- 沒(méi)有空閑對(duì)象或 span 已經(jīng)被 mcache 緩存的 span 列表
nonempty mspanList -- 有空閑對(duì)象的 span 列表
每一個(gè) mcentral 結(jié)構(gòu)體都維護(hù)在 mheap 結(jié)構(gòu)體內(nèi)。
Go 使用 mheap 對(duì)象管理堆,只有一個(gè)全局變量。持有虛擬地址空間。
就上我們從上圖看到的:mheap 存儲(chǔ)了 mcentral 的數(shù)組。這個(gè)數(shù)組包含了各個(gè)的 span 的 mcentral。
central [numSpanClasses]struct { mcentral mcentral pad [unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte }
由于我們有各個(gè)規(guī)格的 span 的 mcentral,當(dāng)一個(gè) mcache 從 mcentral 申請(qǐng) mspan 時(shí),只需要在獨(dú)立的 mcentral 級(jí)別中使用鎖,所以其它任何 mcache 在同一時(shí)間申請(qǐng)不同大小規(guī)格的 mspan 將互不受影響可以正常申請(qǐng)。
pad為格外增加的字節(jié)。對(duì)齊填充(Pad)用于確保 mcentrals 以 CacheLineSize 個(gè)字節(jié)數(shù)分隔,所以每一個(gè) MCentral.lock 都可以獲取自己的緩存行(cache line),以避免偽共享(false sharing)問(wèn)題。
圖中對(duì)應(yīng)的free[_MaxMHeapList]mSpanList
:一個(gè) spanList 數(shù)組。每一個(gè) spanList 中的 mspan 包含 1 ~ 127(_MaxMHeapList - 1)個(gè)頁(yè)。例如,free[3] 是一個(gè)包含 3 個(gè)頁(yè)的 mspan 鏈表。free 表示 free list,表示未分配。對(duì)應(yīng) busy list。
freelarge mSpanList:一個(gè) mspan 的列表,每一個(gè)元素(mspan)的頁(yè)數(shù)大于 127,通過(guò) mtreap 結(jié)構(gòu)體管理。busylarge與之相對(duì)應(yīng)。
在進(jìn)行內(nèi)存分配時(shí),go按照大小分成3種對(duì)象類(lèi)
小于16個(gè)字節(jié)的對(duì)象Tiny類(lèi)
適用于大32 kB的Small類(lèi)
適用于大對(duì)象的large類(lèi)
Small類(lèi)會(huì)被分為大約有70個(gè)大小,每一個(gè)大小都擁有一個(gè)free list
引入Tiny這一微小對(duì)象是為了適應(yīng)小字符串和獨(dú)立的轉(zhuǎn)義變量。
Tiny微小對(duì)象將幾個(gè)微小的分配請(qǐng)求組合到一個(gè)16字節(jié)的內(nèi)存塊中
當(dāng)分配Tiny對(duì)象時(shí):
查看協(xié)程的mcache的相應(yīng)tiny槽
根據(jù)分配對(duì)象的大小,將現(xiàn)有子對(duì)象(如果存在)的大小四舍五入為8、4或2個(gè)字節(jié)
如果當(dāng)前分配對(duì)象與現(xiàn)有tiny子對(duì)象適合,請(qǐng)將其放置在此處
如果tiny槽未發(fā)現(xiàn)合適的塊:
查看協(xié)程的mcache
中相應(yīng)的mspan
掃描mspan
的bitmap
以找到可用插槽
如果有空閑插槽,對(duì)其進(jìn)行分配并將其用作新的小型插槽對(duì)象(這一切都可以在不獲取鎖的情況下完成)
如果mspan
沒(méi)有可用插槽:
從mcentral
的所需大小類(lèi)的mspan
列表中獲得一個(gè)新的mspan
如果mspan
的列表為空:
從mheap
獲取內(nèi)存頁(yè)以用于mspan
如果mheap
為空或沒(méi)有足夠大的內(nèi)存頁(yè)
從操作系統(tǒng)中分配一組新的頁(yè)(至少1MB)
Go 會(huì)在操作系統(tǒng)分配超大的頁(yè)(稱(chēng)作 arena),分配大量?jī)?nèi)存頁(yè)將分?jǐn)偱cOS溝通的成本
small對(duì)象分配與Tiny對(duì)象類(lèi)似,
分配和釋放大對(duì)象直接使用mheap
,就像在TCMalloc中一樣,管理了一組free list
大對(duì)象被四舍五入為頁(yè)大?。?K)的倍數(shù),在free list中查找第k個(gè)free list,如果其為空,則繼續(xù)查找更大的一個(gè)free list,直到第128個(gè)free list
如果在第127個(gè)free list中找不到,我們?cè)谑S嗟拇髢?nèi)存頁(yè)(mspan.freelarge
字段)中查找跨度,如果失敗,則從操作系統(tǒng)獲取。
關(guān)于golang的內(nèi)存分配就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果喜歡這篇文章,不如把它分享出去讓更多的人看到。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性?xún)r(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專(zhuān)為企業(yè)上云打造定制,能夠滿(mǎn)足用戶(hù)豐富、多元化的應(yīng)用場(chǎng)景需求。