golang學習之路--內存分配器

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"一.前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 筆者在經過了前期基礎學習後,用go語言來實現自己面臨的業務問題已經不再是問題,所以擁有了另一方面的求知慾--go語言自身的各種包,各種機制是如何實現的,本章主要在探究go語言的內存分配器,希望能用本文講清楚go語言內存分配器的機制,幫助大家更好地理解go語言的運行機制。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"二.簡介","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 不同於c語言使用malloc和free來主動管理內存,golang讓程序員避免了這一切繁雜的操作,它通過","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"escape analysis","attrs":{}},{"type":"text","text":"來分配內存,通過","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"garbage collection","attrs":{}},{"type":"text","text":"(gc)來回收內存,本文主要介紹內存分配部分的工作。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"三.詳細解釋 ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.1 golang內存分配時機","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序有兩種內存,一種是堆棧(stack),一種是堆(heap),所有的堆內數據都被GC管理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們要明白什麼時候程序會分配內存,在某些語言中是程序員主動申請的,在go語言中則依賴escape analysis,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"越多的值在堆棧,程序運行越快(","attrs":{}},{"type":"text","text":"存取速度比堆要快,僅次於直接位於CPU中的寄存器","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":")","attrs":{}},{"type":"text","text":",以下是內存分配的一些時機","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. goloang只會把函數中確定不在函數結束後使用的變量放到堆棧,否則就會放到堆:一個值可能在構造該值的函數之後被引用-->變量上傳","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package main\n\n\nfunc main(){\n\tn:=answer()\n\tprintln(*n/2)\n}\n\nfunc answer() *int{\n\tx:=42\n\treturn &x\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用命令 go build -gcflags=\"-m -l\"得到結果","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"./main.go:10:2: moved to heap: x","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.編譯器確定值太大而無法放入堆棧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.編譯器在編譯的時候無法得知這個值的具體大小","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps:將變量下傳,變量還會留在堆棧中","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type Reader interface{\n Read(p []byte) (n int,err error)\n}\n//better than \ntype Reader interface{\n Read(n int) (b []byte,err error)\n}\n//因爲從上面傳參數下去用的是堆棧,從下面往上傳,則會escape到堆,導致程序更慢","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.2 golang內存分配方式","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.1 TCMalloc","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"學習go語言的內存分配方式之前,我們先來看看另一個內存分配器-->TCMalloc,全稱","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Thread-Caching Malloc","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCMalloc有兩個重要組成部分:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程內存(thread cache)","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"頁堆(page heap)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.1.1 線程內存","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每一個內存頁都被分爲多個固定分配大小規格的空閒列表(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"free list","attrs":{}}],"attrs":{}},{"type":"text","text":") 用於減少碎片化。這樣每一個線程都可以獲得一個用於無鎖分配小對象的緩存,這樣可以讓並行程序分配小對象(<=32KB)非常高效。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4c/4c84cb9fb840219078d9aff85b382722.png","alt":"Thread Cache (Each Thread gets this Thread Local Thread Cache)","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如圖所示,第一行就是長度爲8字節的內存塊,在thread cache內最大的爲256字節的內存塊","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.1.2 頁堆","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCMalloc 管理的堆由一組頁組成(page一般大小爲4kb),","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一組連續的頁面被表示爲 span","attrs":{}},{"type":"text","text":"。當分配的對象大於 32KB,將使用頁堆(Page Heap)進行內存分配,分配對象時,大的對象直接分配 Span,小的對象從 Span 中分配。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ee/ee1f5db24330579506e2422cde42cb62.png","alt":"Page Heap (for span management)","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當沒有足夠的空間分配小對象則會到頁堆獲取內存。如果頁堆頁沒有足夠的內存,則頁堆會向操作系統申請更多的內存。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.1.3 內存分配器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將基於 Page 的對象分配,和Page本身的管理串聯","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f2/f26fa0ed9bc1c6b7bddca6cac1147034.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每種規格的對象,都從不同的 Span 進行分配;每種規則的對象都有一個獨立的內存分配單元:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"CentralCache","attrs":{}},{"type":"text","text":"。在一個CentralCache 內存,我們用鏈表把所有 Span 組織起來,每次需要分配時就找一個 Span 從中分配一個 Object;當沒有空閒的 Span 時,就從 PageHeap 申請 Span。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.1.3 總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終我們得到結構圖如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/32/32701308549f2105ea681dc317004ddd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCMalloc針對不同的對象分配採用了不同的形式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個線程都一個線程局部的 ThreadCache,按照不同的規格,維護了對象的鏈表;如果ThreadCache 的對象不夠了,就從 CentralCache 進行批量分配;如果 CentralCache 依然沒有,就從PageHeap申請Span;如果 PageHeap沒有合適的 Page,就只能從操作系統申請了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在釋放內存的時候,ThreadCache依然遵循批量釋放的策略,對象積累到一定程度就釋放給 CentralCache;CentralCache發現一個 Span的內存完全釋放了,就可以把這個 Span 歸還給 PageHeap;PageHeap發現一批連續的Page都釋放了,就可以歸還給操作系統。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此,TCMalloc的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"核心思路","attrs":{}},{"type":"text","text":"即:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"把內存分爲多級管理,從而降低鎖的粒度。它將可用的堆內存採用二級分配的方式進行管理:每個線程都會自行維護一個獨立的內存池,進行內存分配時優先從該內存池中分配,當內存池不足時纔會向全局內存池申請,以避免不同線程對全局內存池的頻繁競爭。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.2 Go內存分配器結構","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.2.1 初始化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go在程序啓動的時候,會先向操作系統申請一塊內存(注意這時還只是一段虛擬的地址空間,並不會真正地分配內存),切成小塊後自己進行管理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"申請到的內存塊被分配了三個區域,在X64上分別是512MB,16GB,512GB大小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/ec26bf0d8d9606bcc8a79b97c6cecad2.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"arena區域","attrs":{}}],"attrs":{}},{"type":"text","text":"就是我們所謂的堆區,Go動態分配的內存都是在這個區域,它把內存分割成","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"8KB","attrs":{}}],"attrs":{}},{"type":"text","text":"大小的頁,一些頁組合起來稱爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"bitmap區域","attrs":{}}],"attrs":{}},{"type":"text","text":"標識","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"區域哪些地址保存了對象,並且用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"4bit","attrs":{}}],"attrs":{}},{"type":"text","text":"標誌位表示對象是否包含指針、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"GC","attrs":{}}],"attrs":{}},{"type":"text","text":"標記信息。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"bitmap","attrs":{}}],"attrs":{}},{"type":"text","text":"中一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"byte","attrs":{}}],"attrs":{}},{"type":"text","text":"大小的內存對應","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"區域中4個指針大小(指針大小爲 8B )的內存,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"bitmap","attrs":{}}],"attrs":{}},{"type":"text","text":"區域的大小是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"512GB/(4*8B)=16GB","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"spans區域","attrs":{}}],"attrs":{}},{"type":"text","text":"存放","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"(也就是一些","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"分割的頁組合起來的內存管理基本單元,後文會再講)的指針,每個指針對應一頁,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域的大小就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"512GB/8KB*8B=512MB","attrs":{}}],"attrs":{}},{"type":"text","text":"。除以8KB是計算","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"區域的頁數,而最後乘以8是計算","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域所有指針的大小。創建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"的時候,按頁填充對應的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域,在回收","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"object","attrs":{}}],"attrs":{}},{"type":"text","text":"時,根據地址很容易就能找到它所屬的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"spans區域","attrs":{}}],"attrs":{}},{"type":"text","text":"存放","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"(也就是一些","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"分割的頁組合起來的內存管理基本單元,後文會再講)的指針,每個指針對應一頁,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域的大小就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"512GB/8KB*8B=512MB","attrs":{}}],"attrs":{}},{"type":"text","text":"。除以8KB是計算","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"arena","attrs":{}}],"attrs":{}},{"type":"text","text":"區域的頁數,而最後乘以8是計算","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域所有指針的大小。創建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"的時候,按頁填充對應的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"spans","attrs":{}}],"attrs":{}},{"type":"text","text":"區域,在回收","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"object","attrs":{}}],"attrs":{}},{"type":"text","text":"時,根據地址很容易就能找到它所屬的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"go初始化的時候會將內存頁分爲如下67個不同大小的內存塊,最大到32kb","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/45d9e38ebe808a1033386a01a5449031.png","alt":"Size Classes in Go","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.2.2 結構及流程總覽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/03/03e096b295a347d4ab0388f1b97f6d7b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go的內存分配器在分配對象時,根據對象的大小,分成三類:小對象(小於等於16B)、一般對象(大於16B,小於等於32KB)、大對象(大於32KB)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大體上的分配流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.32KB 的對象,直接從mheap上分配;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.<=16B 的對象使用mcache的tiny分配器分配;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.(16B,32KB] 的對象,首先計算對象的規格大小,然後使用mcache中相應規格大小的mspan分配;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.如果mcache沒有相應規格大小的mspan,則向mcentral申請","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5.如果mcentral沒有相應規格大小的mspan,則向mheap申請","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"6.如果mheap中也沒有合適大小的mspan,則向操作系統申請","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3.2.2.3 自底向上名詞解釋","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.2.3.1 內存管理單元","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mspan","attrs":{}},{"type":"text","text":":Go中內存管理的基本單元,是由一片連續的8KB的頁組成的大塊內存。是一個包含起始地址、mspan規格、頁的數量等內容的雙端鏈表,mspan由一組連續的頁組成,按照一定大小劃分成","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"object","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"結構圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bfe07943cfaa79425b3d89d35336b59f.png","alt":"Illustrative Representation of a mspan in Go memory allocator","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.2.3.2 內存管理元件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mcache","attrs":{}},{"type":"text","text":":Go 像 TCMalloc 一樣爲每一個 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"邏輯處理器(P)(Logical Processors)","attrs":{}},{"type":"text","text":" 提供一個本地線程緩存(Local Thread Cache)稱作 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mcache","attrs":{}},{"type":"text","text":",所以如果 Goroutine 需要內存可以直接從 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mcache","attrs":{}},{"type":"text","text":" 中獲取,由於在同一時間只有一個 Goroutine 運行在 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"邏輯處理器(P)(Logical Processors)","attrs":{}},{"type":"text","text":" 上,所以中間不需要任何鎖的參與。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於每一種大小規格都有兩個類型:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"scan","attrs":{}},{"type":"text","text":" -- 包含指針的對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"noscan","attrs":{}},{"type":"text","text":" -- 不包含指針的對象。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"採用這種方法的好處之一就是進行垃圾回收時 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"noscan","attrs":{}},{"type":"text","text":" 對象無需進一步掃描是否引用其他活躍的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(<=16B 的對象使用mcache的tiny分配器分配)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結構體","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"//path: /usr/local/go/src/runtime/mcache.go\n\ntype mcache struct {\n alloc [numSpanClasses]*mspan\n}\n\nnumSpanClasses = _NumSizeClasses << 1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"結構圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b21c2ae3c7ecf2d3a643ba199c696e03.png","alt":"Illustrative Representation of a Relationship between P, mcache, and mspan in Go.","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"central(mcentral)","attrs":{}},{"type":"text","text":":爲所有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcache","attrs":{}}],"attrs":{}},{"type":"text","text":"提供切分好的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"資源。每個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"central","attrs":{}}],"attrs":{}},{"type":"text","text":"保存一種特定大小的全局","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"列表,包括已分配出去的和未分配出去的。 每個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"對應一種","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":",而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"的種類導致它分割的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"object","attrs":{}}],"attrs":{}},{"type":"text","text":"大小不同。當工作線程的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcache","attrs":{}}],"attrs":{}},{"type":"text","text":"中沒有合適(也就是特定大小的)的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"時就會從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"獲取。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"被所有的工作線程共同享有,存在多個Goroutine競爭的情況,因此會消耗鎖資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//path: /usr/local/go/src/runtime/mcentral.go\n\ntype mcentral struct {\n // 互斥鎖\n lock mutex \n // 規格\n sizeclass int32 \n // 尚有空閒object的mspan鏈表\n nonempty mSpanList \n // 沒有空閒object的mspan鏈表,或者是已被mcache取走的msapn鏈表\n empty mSpanList \n // 已累計分配的對象個數\n nmalloc uint64 \n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"結構圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc5c89cb786078c128f6d6901cf8791e.png","alt":"Illustrative Representation of a mcentral","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mheap","attrs":{}},{"type":"text","text":":代表Go程序持有的所有堆空間,Go程序使用一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"的全局對象","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"來管理堆內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"沒有空閒的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"時,會向","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"申請。而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"沒有資源時,會向操作系統申請新內存。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"主要用於大對象的內存分配,以及管理未切割的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":",用於給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"切割成小對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時我們也看到,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mheap","attrs":{}}],"attrs":{}},{"type":"text","text":"中含有所有規格的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":",所以,當一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcache","attrs":{}}],"attrs":{}},{"type":"text","text":"從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"申請","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"時,只需要在獨立的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mcentral","attrs":{}}],"attrs":{}},{"type":"text","text":"中使用鎖,並不會影響申請其他規格的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mspan","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"//path: /usr/local/go/src/runtime/mheap.go\n\ntype mheap struct {\n lock mutex\n // spans: 指向mspans區域,用於映射mspan和page的關係\n spans []*mspan \n // 指向bitmap首地址,bitmap是從高地址向低地址增長的\n bitmap uintptr \n\n // 指示arena區首地址\n arena_start uintptr \n // 指示arena區已使用地址位置\n arena_used uintptr \n // 指示arena區末地址\n arena_end uintptr \n\n central [67*2]struct {\n mcentral mcentral\n pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"結構圖","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35f3dc538f24313b7b7b5ef7dc187796.png","alt":"Illustrative Representation of a mheap.","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"arena","attrs":{}},{"type":"text","text":":golang中所有堆區的統稱,以x64爲例子就是512GB的虛擬地址空間。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"四.之後目標","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.go語言的垃圾回收","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.進程調度,線程調度,協程調度","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.虛擬內存 ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"五.參考學習","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.youtube.com/watch?v=ZMZpH4yT7M0","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.linuxzen.com/go-memory-allocator-visual-guide.html","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://zhuanlan.zhihu.com/p/29216091","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://zhuanlan.zhihu.com/p/59125443","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章