1. 程式人生 > >02.1跟雨痕看go原始碼-記憶體分配(分配&回收)

02.1跟雨痕看go原始碼-記憶體分配(分配&回收)

記憶體分配和垃圾回收有關,這裡我們可以先看一下記憶體分配。
垃圾回收比較複雜,後面講。

一開始雨痕大大說了幾個基本策略:

  1. 每次從作業系統申請一大塊記憶體(比如1MB),以減少系統呼叫。
  2. 將申請到的大塊記憶體按預定大小預先切分成小塊,構成連結串列。
  3. 為物件分配記憶體時,只需從大小合適的連結串列中提取一個小塊即可。
  4. 回收物件記憶體時,將該小塊記憶體重新歸還到原連結串列,以便複用。
  5. 如果閒置記憶體過多,則嘗試歸還部分記憶體給作業系統,降低記憶體開銷。

裡面提到了兩個概念
span: 就是1提到的大塊記憶體被切成小塊的連結串列。
object: 就是上面說的小塊。

span的結構

記憶體結構預分配圖
這裡span雖然是按照小塊的規格進行了分級,但實際上採取了比較靈活的策略,可能會在必要條件下把較大規格span的連結串列的一部分借給較小規格的span,實際合併時也會嘗試合併相鄰的span。

����golang直接採用tcmalloc的成熟架構。
cache central heap
這裡做了三級快取。

cache是每個golang裡面的P搞一個,提供一個無鎖分配。

然後central根據sizeclass的大小,把所有中等的object分成若干等級,然後在這裡做其中一級快取,減少計算量。。

heap則是最後一道快取,在這裡發起回收和mmap申請。

這裡寫圖片描述

然後雨痕給了圖說明的記憶體分佈以及初始化的過程。
還順帶普及了一下內聯優化。某些簡單的函式塊可能會被優化掉,裡面該從堆分配的,卻被弄到了棧分配。。。

cache部分的邏輯:

從程式碼中可以看出tiny&large object做了特定的處理。tiny是使用classsize=2的span,然後裡面使用盡量共享空間,希望一個object能夠多複用幾次。用cache.tinyoffset, cache.tiny進行控制。

大物件則直接使用堆分配。。

同時還介紹了一個
systemstack(func() {
s = largeAlloc(size, uint32(flags))
})這個使用系統棧來執行的分配函式,保證全域性同步。

mcentral部分的邏輯:

這個傢伙內部也有兩個小快取。
這裡寫圖片描述
這還略屌的。
仔細看程式碼可以知道,mcentral的細節策略還是很多的,比如先考慮nonempty再考慮empty,使用empty時要先來個msweep嘗試一下。。。是在不行了才去找heap。。。

mheap部分的邏輯:

和預料的基本一致,作為最後一級的快取。會先考慮自己的free,不行了,才考慮向系統申請。裡面的freelarge是個連結串列在free沒有的時候,會用freelarge,順序遍歷連結串列找出最合適(在大於目標size裡面最小)的freelarge。

#
#

回收

上來就一個綜述:回收不會盯著object而是整個span。
大概就是遍歷span將不可達object合併到freelist,如果已經回收所有object,則將這塊記憶體還給heap。 細節部分,這裡它會考慮同右側的span進行合併。

釋放

這裡的入口有sysmon發起。每5分鐘都來一次。
大概就是把heap裡面的free和freelarge的span list給拿出來幹掉。
細節方面這裡用了madvise,用來在某些場景下提升效能。

其他

大概就是還有四塊系統的部分也需要垃圾回收。
它們自己內部也弄了個二級快取。
這裡就比較簡單了,是fix size的,沒有span、object、classsize,第一級別用了而是一個chunk,還有複用的list。。。第二級就是使用chunk。
還有個什麼record的函式。
大概就是為了提高span的便利效率而做了一個數組型別的h_spans, 然後在特定條件下引發擴容,每次擴容 A*3/2 +1。