詳解Go語言的內(nèi)存模型及堆的分配管理
從非常宏觀的角度看,Go的內(nèi)存管理就是下圖這個樣子。
Go這門語言拋棄了C/C++中的開發(fā)者管理內(nèi)存的方式,實現(xiàn)了主動申請與主動釋放管理,增加了逃逸分析和GC,將開發(fā)者從內(nèi)存管理中釋放出來,讓開發(fā)者有更多的精力去關(guān)注軟件設(shè)計,而不是底層的內(nèi)存問題。這是Go語言成為高生產(chǎn)力語言的原因之一。
我們不需要精通內(nèi)存的管理,因為它確實很復(fù)雜,但掌握內(nèi)存的管理,可以讓你寫出更高質(zhì)量的代碼,另外,還能助你定位Bug。這篇文章采用層層遞進(jìn)的方式,依次會介紹關(guān)于存儲的基本知識,Go內(nèi)存管理的 “前輩” TCMalloc,然后是Go的內(nèi)存管理和分配,最后是總結(jié)。這么做的目的是,希望各位能通過全局的認(rèn)識和思考,擁有更好的編碼思維和架構(gòu)思維。
正文
1. 存儲基礎(chǔ)知識回顧
這部分我們簡單回顧一下計算機(jī)存儲體系、虛擬內(nèi)存、棧和堆,以及堆內(nèi)存的管理,這部分內(nèi)容對理解和掌握Go內(nèi)存管理比較重要。
1.1. 存儲金字塔
這幅圖表達(dá)了計算機(jī)的存儲體系,從上至下的訪問速度越來越慢,訪問時間越來越長。從上至下依次是:
- CPU寄存器
- CPU Cache
- 內(nèi)存
- 硬盤等輔助存儲設(shè)備
- 鼠標(biāo)等外接設(shè)備
你有沒有思考過下面2個簡單的問題,如果沒有不妨想想:
- 如果CPU直接訪問硬盤,CPU能充分利用嗎?
- 如果CPU直接訪問內(nèi)存,CPU能充分利用嗎?
CPU速度很快,但硬盤等持久存儲很慢,如果CPU直接訪問磁盤,磁盤可以拉低CPU的速度,機(jī)器整體性能就會低下,為了彌補(bǔ)這2個硬件之間的速率差異,所以在CPU和磁盤之間增加了比磁盤快很多的內(nèi)存。
然而,CPU跟內(nèi)存的速率也不是相同的,從上圖可以看到,CPU的速率提高的很快(摩爾定律),然而內(nèi)存速率增長的很慢,雖然CPU的速率現(xiàn)在增加的很慢了,但是內(nèi)存的速率也沒增加多少,速率差距很大,從1980年開始CPU和內(nèi)存速率差距在不斷拉大,為了彌補(bǔ)這2個硬件之間的速率差異,所以在CPU跟內(nèi)存之間增加了比內(nèi)存更快的Cache,Cache是內(nèi)存數(shù)據(jù)的緩存,可以降低CPU訪問內(nèi)存的時間。
三級Cache分別是L1、L2、L3,它們的速率是三個不同的層級,L1速率最快,與CPU速率最接近,是RAM速率的100倍,L2速率就降到了RAM的25倍,L3的速率更靠近RAM的速率。
看到這了,你有沒有Get到整個存儲體系的分層設(shè)計?自頂向下,速率越來越低,訪問時間越來越長,從磁盤到CPU寄存器,上一層都可以看做是下一層的緩存??戳朔謱釉O(shè)計,下面開始正式介紹內(nèi)存。
1.2. 虛擬內(nèi)存
虛擬內(nèi)存是當(dāng)代操作系統(tǒng)必備的一項重要功能,對于進(jìn)程而言虛擬內(nèi)存屏蔽了底層了RAM和磁盤,并向進(jìn)程提供了遠(yuǎn)超物理內(nèi)存大小的內(nèi)存空間。我們看一下虛擬內(nèi)存的分層設(shè)計。
上圖展示了某進(jìn)程訪問數(shù)據(jù),當(dāng)Cache沒有命中的時候,訪問虛擬內(nèi)存獲取數(shù)據(jù)的過程。在訪問內(nèi)存,實際訪問的是虛擬內(nèi)存,虛擬內(nèi)存通過頁表查看,當(dāng)前要訪問的虛擬內(nèi)存地址,是否已經(jīng)加載到了物理內(nèi)存。如果已經(jīng)在物理內(nèi)存,則取物理內(nèi)存數(shù)據(jù),如果沒有對應(yīng)的物理內(nèi)存,則從磁盤加載數(shù)據(jù)到物理內(nèi)存,并把物理內(nèi)存地址和虛擬內(nèi)存地址更新到頁表。
物理內(nèi)存就是磁盤存儲緩存層,在沒有虛擬內(nèi)存的時代,物理內(nèi)存對所有進(jìn)程是共享的,多進(jìn)程同時訪問同一個物理內(nèi)存會存在并發(fā)問題。而引入虛擬內(nèi)存后,每個進(jìn)程都有各自的虛擬內(nèi)存,內(nèi)存的并發(fā)訪問問題的粒度從多進(jìn)程級別,可以降低到多線程級別。
1.3. 棧和堆
我們現(xiàn)在從虛擬內(nèi)存,再進(jìn)一層,看虛擬內(nèi)存中的棧和堆,也就是進(jìn)程對內(nèi)存的管理。
上圖展示了一個進(jìn)程的虛擬內(nèi)存劃分,代碼中使用的內(nèi)存地址都是虛擬內(nèi)存地址,而不是實際的物理內(nèi)存地址。棧和堆只是虛擬內(nèi)存上2塊不同功能的內(nèi)存區(qū)域:
棧在高地址,從高地址向低地址增長
堆在低地址,從低地址向高地址增長
棧和堆相比有這么幾個好處:
- 棧的內(nèi)存管理簡單,分配比堆上快。
- 棧的內(nèi)存不需要回收,而堆需要進(jìn)行回收,無論是主動free,還是被動的垃圾回收,這都需要花費額外的CPU。
- 棧上的內(nèi)存有更好的局部性,堆上內(nèi)存訪問就不那么友好了,CPU訪問的2塊數(shù)據(jù)可能在不同的頁上,CPU訪問數(shù)據(jù)的時間可能就上去了。
1.4. 堆內(nèi)存管理
我們再進(jìn)一層,當(dāng)我們說內(nèi)存管理的時候,主要是指堆內(nèi)存的管理,因為棧的內(nèi)存管理不需要程序去操心,這小節(jié)看下堆內(nèi)存管理到底完成了什么。如上圖所示主要是3部分,分別是分配內(nèi)存塊,回收內(nèi)存塊和組織內(nèi)存塊。
在一個最簡單的內(nèi)存管理中,堆內(nèi)存最初會是一個完整的大塊,即未分配任何內(nèi)存。當(dāng)發(fā)現(xiàn)內(nèi)存申請的時候,堆內(nèi)存就會從未分配內(nèi)存分割出一個小內(nèi)存塊(block),然后用鏈表把所有內(nèi)存塊連接起來。需要一些信息描述每個內(nèi)存塊的基本信息,比如大小(size)、是否使用中(used)和下一個內(nèi)存塊的地址(next),內(nèi)存塊實際數(shù)據(jù)存儲在data中。
一個內(nèi)存塊包含了3類信息,如下圖所示,元數(shù)據(jù)、用戶數(shù)據(jù)和對齊字段,內(nèi)存對齊是為了提高訪問效率。下圖申請5Byte內(nèi)存的時候,就需要進(jìn)行內(nèi)存對齊。
釋放內(nèi)存實質(zhì)是把使用的內(nèi)存塊從鏈表中取出來,然后標(biāo)記為未使用,當(dāng)分配內(nèi)存塊的時候,可以從未使用內(nèi)存塊中優(yōu)先查找大小相近的內(nèi)存塊,如果找不到,再從未分配的內(nèi)存中分配內(nèi)存。
上面這個簡單的設(shè)計中還沒考慮內(nèi)存碎片的問題,因為隨著內(nèi)存不斷的申請和釋放,內(nèi)存上會存在大量的碎片,降低內(nèi)存的使用率。為了解決內(nèi)存碎片,可以將2個連續(xù)的未使用的內(nèi)存塊合并,減少碎片。
以上就是內(nèi)存管理的基本思路,關(guān)于基本的內(nèi)存管理,想了解更多,可以閱讀這篇文章《Writing a Memory Allocator》,本節(jié)的3張圖片也是來自這篇文章。
2. TCMalloc
TCMalloc是Thread Cache Malloc的簡稱,是Go內(nèi)存管理的起源,Go的內(nèi)存管理是借鑒了TCMalloc,隨著Go的迭代,Go的內(nèi)存管理與TCMalloc不一致地方在不斷擴(kuò)大,但其主要思想、原理和概念都是和TCMalloc一致的,如果跳過TCMalloc直接去看Go的內(nèi)存管理,也許你會似懂非懂。
掌握TCMalloc的理念,無需去關(guān)注過多的源碼細(xì)節(jié),就可以為掌握Go的內(nèi)存管理打好基礎(chǔ),基礎(chǔ)打好了,后面知識才扎實。
在Linux操作系統(tǒng)中,其實有不少的內(nèi)存管理庫,比如glibc的ptmalloc,F(xiàn)reeBSD的jemalloc,Google的tcmalloc等等,為何會出現(xiàn)這么多的內(nèi)存管理庫?本質(zhì)都是在多線程編程下,追求更高內(nèi)存管理效率:更快的分配是主要目的。
我們前面提到引入虛擬內(nèi)存后,讓內(nèi)存的并發(fā)訪問問題的粒度從多進(jìn)程級別,降低到多線程級別。然而同一進(jìn)程下的所有線程共享相同的內(nèi)存空間,它們申請內(nèi)存時需要加鎖,如果不加鎖就存在同一塊內(nèi)存被2個線程同時訪問的問題。
TCMalloc的做法是什么呢?為每個線程預(yù)分配一塊緩存,線程申請小內(nèi)存時,可以從緩存分配內(nèi)存,這樣有2個好處:
為線程預(yù)分配緩存需要進(jìn)行1次系統(tǒng)調(diào)用,后續(xù)線程申請小內(nèi)存時直接從緩存分配,都是在用戶態(tài)執(zhí)行的,沒有了系統(tǒng)調(diào)用,縮短了內(nèi)存總體的分配和釋放時間,這是快速分配內(nèi)存的第二個層次。
多個線程同時申請小內(nèi)存時,從各自的緩存分配,訪問的是不同的地址空間,從而無需加鎖,把內(nèi)存并發(fā)訪問的粒度進(jìn)一步降低了,這是快速分配內(nèi)存的第三個層次。
2.1. 基本原理
下面就簡單介紹下TCMalloc,細(xì)致程度夠我們理解Go的內(nèi)存管理即可。
結(jié)合上圖,介紹TCMalloc的幾個重要概念:
- Page
操作系統(tǒng)對內(nèi)存管理以頁為單位,TCMalloc也是這樣,只不過TCMalloc里的Page大小與操作系統(tǒng)里的大小并不一定相等,而是倍數(shù)關(guān)系?!禩CMalloc解密》里稱x64下Page大小是8KB。
- Span
一組連續(xù)的Page被稱為Span,比如可以有2個頁大小的Span,也可以有16頁大小的Span,Span比Page高一個層級,是為了方便管理一定大小的內(nèi)存區(qū)域,Span是TCMalloc中內(nèi)存管理的基本單位。
- ThreadCache
ThreadCache是每個線程各自的Cache,一個Cache包含多個空閑內(nèi)存塊鏈表,每個鏈表連接的都是內(nèi)存塊,同一個鏈表上內(nèi)存塊的大小是相同的,也可以說按內(nèi)存塊大小,給內(nèi)存塊分了個類,這樣可以根據(jù)申請的內(nèi)存大小,快速從合適的鏈表選擇空閑內(nèi)存塊。由于每個線程有自己的ThreadCache,所以ThreadCache訪問是無鎖的。
- CentralCache
CentralCache是所有線程共享的緩存,也是保存的空閑內(nèi)存塊鏈表,鏈表的數(shù)量與ThreadCache中鏈表數(shù)量相同,當(dāng)ThreadCache的內(nèi)存塊不足時,可以從CentralCache獲取內(nèi)存塊;當(dāng)ThreadCache內(nèi)存塊過多時,可以放回CentralCache。由于CentralCache是共享的,所以它的訪問是要加鎖的。
- PageHeap
PageHeap是對堆內(nèi)存的抽象,PageHeap存的也是若干鏈表,鏈表保存的是Span。當(dāng)CentralCache的內(nèi)存不足時,會從PageHeap獲取空閑的內(nèi)存Span,然后把1個Span拆成若干內(nèi)存塊,添加到對應(yīng)大小的鏈表中并分配內(nèi)存;當(dāng)CentralCache的內(nèi)存過多時,會把空閑的內(nèi)存塊放回PageHeap中。
如下圖所示,分別是1頁Page的Span鏈表,2頁Page的Span鏈表等,最后是large span set,這個是用來保存中大對象的。毫無疑問,PageHeap也是要加鎖的。
- Page
與TCMalloc中的Page相同,x64架構(gòu)下1個Page的大小是8KB。上圖的最下方,1個淺藍(lán)色的長方形代表1個Page。
- Span
Span與TCMalloc中的Span相同,Span是內(nèi)存管理的基本單位,代碼中為mspan,一組連續(xù)的Page組成1個Span,所以上圖一組連續(xù)的淺藍(lán)色長方形代表的是一組Page組成的1個Span,另外,1個淡紫色長方形為1個Span。
- mcache
mcache與TCMalloc中的ThreadCache類似,mcache保存的是各種大小的Span,并按Span class分類,小對象直接從mcache分配內(nèi)存,它起到了緩存的作用,并且可以無鎖訪問。但是mcache與ThreadCache也有不同點,TCMalloc中是每個線程1個ThreadCache,Go中是每個P擁有1個mcache。因為在Go程序中,當(dāng)前最多有GOMAXPROCS個線程在運行,所以最多需要GOMAXPROCS個mcache就可以保證各線程對mcache的無鎖訪問,線程的運行又是與P綁定的,把mcache交給P剛剛好。
- mcentral
mcentral與TCMalloc中的CentralCache類似,是所有線程共享的緩存,需要加鎖訪問。它按Span級別對Span分類,然后串聯(lián)成鏈表,當(dāng)mcache的某個級別Span的內(nèi)存被分配光時,它會向mcentral申請1個當(dāng)前級別的Span。
但是mcentral與CentralCache也有不同點,CentralCache是每個級別的Span有1個鏈表,mcache是每個級別的Span有2個鏈表,這和mcache申請內(nèi)存有關(guān),稍后我們再解釋。
- mheap
mheap與TCMalloc中的PageHeap類似,它是堆內(nèi)存的抽象,把從OS申請出的內(nèi)存頁組織成Span,并保存起來。當(dāng)mcentral的Span不夠用時會向mheap申請內(nèi)存,而mheap的Span不夠用時會向OS申請內(nèi)存。mheap向OS的內(nèi)存申請是按頁來的,然后把申請來的內(nèi)存頁生成Span組織起來,同樣也是需要加鎖訪問的。
但是mheap與PageHeap也有不同點:mheap把Span組織成了樹結(jié)構(gòu),而不是鏈表,并且還是2棵樹,然后把Span分配到heapArena進(jìn)行管理,它包含地址映射和span是否包含指針等位圖,這樣做的主要原因是為了更高效的利用內(nèi)存:分配、回收和再利用。
- object size:代碼里簡稱size,指申請內(nèi)存的對象大小。
- size class:代碼里簡稱class,它是size的級別,相當(dāng)于把size歸類到一定大小的區(qū)間段,比如size[1,8]屬于size class 1,size(8,16]屬于size class 2。
- span class:指span的級別,但span class的大小與span的大小并沒有正比關(guān)系。span class主要用來和size class做對應(yīng),1個size class對應(yīng)2個span class,2個span class的span大小相同,只是功能不同,1個用來存放包含指針的對象,一個用來存放不包含指針的對象,不包含指針對象的Span就無需GC掃描了。
- num of page:代碼里簡稱npage,代表Page的數(shù)量,其實就是Span包含的頁數(shù),用來分配內(nèi)存。
3. Go內(nèi)存分配
Go中的內(nèi)存分類并不像TCMalloc那樣分成小、中、大對象,但是它的小對象里又細(xì)分了一個Tiny對象,Tiny對象指大小在1Byte到16Byte之間并且不包含指針的對象。小對象和大對象只用大小劃定,無其他區(qū)分。
小對象是在mcache中分配的,而大對象是直接從mheap分配的,從小對象的內(nèi)存分配看起。
3.1. 小對象的內(nèi)存分配
大小轉(zhuǎn)換這一小節(jié),我們介紹了轉(zhuǎn)換表,size class從1到66共66個,代碼中_NumSizeClasses=67代表了實際使用的size class數(shù)量,即67個,從0到67,size class 0實際并未使用到。
上文提到1個size class對應(yīng)2個span class:
numSpanClasses = _NumSizeClasses * 2
numSpanClasses為span class的數(shù)量為134個,所以span class的下標(biāo)是從0到133,所以上圖中mcache標(biāo)注了的span class是,span class 0到span class 133。每1個span class都指向1個span,也就是mcache最多有134個span。
- 為對象尋找span
尋找span的流程如下:
- 計算對象所需內(nèi)存大小size
- 根據(jù)size到size class映射,計算出所需的size class
- 根據(jù)size class和對象是否包含指針計算出span class
- 獲取該span class指向的span
以分配一個不包含指針的,大小為24Byte的對象為例,根據(jù)映射表:
// class bytes/obj bytes/span objects tail waste max waste // 1 8 8192 1024 0 87.50% // 2 16 8192 512 0 43.75% // 3 32 8192 256 0 46.88% // 4 48 8192 170 32 31.52%
對應(yīng)的size class為3,它的對象大小范圍是(16,32]Byte,24Byte剛好在此區(qū)間,所以此對象的size class為3。
Size class到span class的計算如下
// noscan為true代表對象不包含指針 func makeSpanClass(sizeclass uint8, noscan bool) spanClass { return spanClass(sizeclass<<1) | spanClass(bool2int(noscan)) }
所以對應(yīng)的span class為7,所以該對象需要的是span class 7指向的span。
span class = 3 << 1 | 1 = 7
- 從span分配對象空間
Span可以按對象大小切成很多份,這些都可以從映射表上計算出來,以size class 3對應(yīng)的span為例,span大小是8KB,每個對象實際所占空間為32Byte,這個span就被分成了256塊,可以根據(jù)span的起始地址計算出每個對象塊的內(nèi)存地址。
隨著內(nèi)存的分配,span中的對象內(nèi)存塊,有些被占用,有些未被占用,比如上圖,整體代表1個span,藍(lán)色塊代表已被占用內(nèi)存,綠色塊代表未被占用內(nèi)存。當(dāng)分配內(nèi)存時,只要快速找到第一個可用的綠色塊,并計算出內(nèi)存地址即可,如果需要還可以對內(nèi)存塊數(shù)據(jù)清零。
當(dāng)span內(nèi)的所有內(nèi)存塊都被占用時,沒有剩余空間繼續(xù)分配對象,mcache會向mcentral申請1個span,mcache拿到span后繼續(xù)分配對象。
- mcache向mcentral申請span
mcentral和mcache一樣,都是0~133這134個span class級別,但每個級別都保存了2個span list,即2個span鏈表:
- nonempty:這個鏈表里的span,所有span都至少有1個空閑的對象空間。這些span是mcache釋放span時加入到該鏈表的。
- empty:這個鏈表里的span,所有的span都不確定里面是否有空閑的對象空間。當(dāng)一個span交給mcache的時候,就會加入到empty鏈表。
這兩個東西名稱一直有點繞,建議直接把empty理解為沒有對象空間就好了。
mcache向mcentral申請span時,mcentral會先從nonempty搜索滿足條件的span,如果沒有找到再從emtpy搜索滿足條件的span,然后把找到的span交給mcache。
- mheap的span管理
mheap里保存了兩棵二叉排序樹,按span的page數(shù)量進(jìn)行排序:
- free:free中保存的span是空閑并且非垃圾回收的span。
- scav:scav中保存的是空閑并且已經(jīng)垃圾回收的span。
如果是垃圾回收導(dǎo)致的span釋放,span會被加入到scav,否則加入到free,比如剛從OS申請的的內(nèi)存也組成的Span。
mheap中還有arenas,由一組heapArena組成,每一個heapArena都包含了連續(xù)的pagesPerArena個span,這個主要是為mheap管理span和垃圾回收服務(wù)。mheap本身是一個全局變量,它里面的數(shù)據(jù),也都是從OS直接申請來的內(nèi)存,并不在mheap所管理的那部分內(nèi)存以內(nèi)。
- mcentral向mheap申請span
當(dāng)mcentral向mcache提供span時,如果empty里也沒有符合條件的span,mcentral會向mheap申請span。
此時,mcentral需要向mheap提供需要的內(nèi)存頁數(shù)和span class級別,然后它優(yōu)先從free中搜索可用的span。如果沒有找到,會從scav中搜索可用的span。如果還沒有找到,它會向OS申請內(nèi)存,再重新搜索2棵樹,必然能找到span。如果找到的span比需要的span大,則把span進(jìn)行分割成2個span,其中1個剛好是需求大小,把剩下的span再加入到free中去,然后設(shè)置需要的span的基本信息,然后交給mcentral。
- mheap向OS申請內(nèi)存
當(dāng)mheap沒有足夠的內(nèi)存時,mheap會向OS申請內(nèi)存,把申請的內(nèi)存頁保存為span,然后把span插入到free樹。在32位系統(tǒng)中,mheap還會預(yù)留一部分空間,當(dāng)mheap沒有空間時,先從預(yù)留空間申請,如果預(yù)留空間內(nèi)存也沒有了,才向OS申請。
3.2. 大對象的內(nèi)存分配
大對象的分配比小對象省事多了,99%的流程與mcentral向mheap申請內(nèi)存的相同,所以不重復(fù)介紹了。不同的一點在于mheap會記錄一點大對象的統(tǒng)計信息,詳情見mheap.alloc_m()。
4. Go垃圾回收和內(nèi)存釋放
如果只申請和分配內(nèi)存,內(nèi)存終將枯竭。Go使用垃圾回收收集不再使用的span,調(diào)用mspan.scavenge()把span釋放還給OS(并非真釋放,只是告訴OS這片內(nèi)存的信息無用了,如果你需要的話,收回去好了),然后交給mheap,mheap對span進(jìn)行span的合并,把合并后的span加入scav樹中,等待再分配內(nèi)存時,由mheap進(jìn)行內(nèi)存再分配,Go垃圾回收也是一個很強(qiáng)的主題,計劃后面單獨寫一篇文章介紹。
現(xiàn)在我們關(guān)注一下,Go程序是怎么把內(nèi)存釋放給操作系統(tǒng)的?釋放內(nèi)存的函數(shù)是sysUnused,它會被mspan.scavenge()調(diào)用:
func sysUnused(v unsafe.Pointer, n uintptr) { // MADV_FREE_REUSABLE is like MADV_FREE except it also propagates // accounting information about the process to task_info. madvise(v, n, _MADV_FREE_REUSABLE) }
注釋說 _MADV_FREE_REUSABLE 與 MADV_FREE 的功能類似,它的功能是給內(nèi)核提供一個建議:這個內(nèi)存地址區(qū)間的內(nèi)存已經(jīng)不再使用,可以進(jìn)行回收。但內(nèi)核是否回收,以及什么時候回收,這就是內(nèi)核的事情了。如果內(nèi)核真把這片內(nèi)存回收了,當(dāng)Go程序再使用這個地址時,內(nèi)核會重新進(jìn)行虛擬地址到物理地址的映射。所以在內(nèi)存充足的情況下,內(nèi)核也沒有必要立刻回收內(nèi)存。
5. Go的棧內(nèi)存
最后提一下棧內(nèi)存。從一個宏觀的角度看,內(nèi)存管理不應(yīng)當(dāng)只有堆,也應(yīng)當(dāng)有棧。每個goroutine都有自己的棧,棧的初始大小是2KB,100萬的goroutine會占用2G,但goroutine的棧會在2KB不夠用時自動擴(kuò)容,當(dāng)擴(kuò)容為4KB的時候,百萬goroutine會占用4GB。
總結(jié)
Go的內(nèi)存分配原理就不再回顧了,它主要強(qiáng)調(diào)兩個重要的思想:
使用緩存提高效率。在存儲的整個體系中到處可見緩存的思想,Go內(nèi)存分配和管理也使用了緩存,利用緩存一是減少了系統(tǒng)調(diào)用的次數(shù),二是降低了鎖的粒度、減少加鎖的次數(shù),從這2點提高了內(nèi)存管理效率。
以空間換時間,提高內(nèi)存管理效率。空間換時間是一種常用的性能優(yōu)化思想,這種思想其實非常普遍,比如Hash、Map、二叉排序樹等數(shù)據(jù)結(jié)構(gòu)的本質(zhì)就是空間換時間,在數(shù)據(jù)庫中也很常見,比如數(shù)據(jù)庫索引、索引視圖和數(shù)據(jù)緩存等,再如Redis等緩存數(shù)據(jù)庫也是空間換時間的思想。
以上就是詳解Go語言的內(nèi)存模型及堆的分配管理的詳細(xì)內(nèi)容,更多關(guān)于Go內(nèi)存模型及堆的分配的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言標(biāo)準(zhǔn)庫中math模塊詳細(xì)功能介紹與示例代碼
Go語言的標(biāo)準(zhǔn)庫math提供了一系列基礎(chǔ)數(shù)學(xué)函數(shù)和常量,用于進(jìn)行科學(xué)計算、幾何計算和其他數(shù)學(xué)相關(guān)的操作,這篇文章主要介紹了Go語言標(biāo)準(zhǔn)庫中math模塊詳細(xì)功能介紹與示例代碼,需要的朋友可以參考下2025-03-03一文帶你吃透Golang中net/http標(biāo)準(zhǔn)庫服務(wù)端
這篇文章將從服務(wù)端(Server)作為切入點和大家分享一下Go語言net/http標(biāo)準(zhǔn)庫的實現(xiàn)邏輯,進(jìn)而一步步分析http標(biāo)準(zhǔn)庫內(nèi)部是如何運作的,感興趣的可以了解下2024-03-03golang中channel+error來做異步錯誤處理有多香
官方推薦golang中錯誤處理當(dāng)做值處理, 既然是值那就可以在channel中傳輸,這篇文章主要介紹了golang 錯誤處理channel+error真的香,需要的朋友可以參考下2023-01-01在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用
這篇文章主要介紹了在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用,來看下?Cobra?的使用,這里我們使用的?go1.13.3?版本,使用?Go?Modules?來進(jìn)行包管理,需要的朋友可以參考下2022-01-01golang中context.WithValue的使用規(guī)范問題小結(jié)
本文主要介紹了golang中context.WithValue的使用規(guī)范問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02一文帶你輕松學(xué)會Go語言動態(tài)調(diào)用函數(shù)
這篇文章主要是帶大家學(xué)習(xí)一下Go語言是如何動態(tài)調(diào)用函數(shù)的,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定的幫助,需要的可以參考下2022-11-11