Go語言輕鬆進階

TIGERB發表於2022-04-15

導讀

本文基於Go原始碼版本1.16、64位Linux平臺、1Page=8KB、本文的記憶體特指虛擬記憶體

今天我們開始進入《Go語言輕鬆進階》系列第二章「記憶體與垃圾回收」第二部分「Go語言記憶體管理」。

關於「記憶體與垃圾回收」章節,會從如下三大部分展開:

第一部分「讀前知識儲備」已經完結,為了更好理解本文大家可以點選歷史連結進行檢視或複習。

目錄

關於講解「Go語言記憶體管理」部分我的思路如下:

  1. 介紹整體架構
  2. 介紹架構設計中一個很有意思的地方
  3. 通過介紹Go記憶體管理中的關鍵結構mspan,帶出pagemspanobjectsizeclassspanclassheaparenachunk的概念
  4. 接著介紹堆記憶體、棧記憶體的分配
  5. 回顧和總結

通過這個思路拆解的目錄:

  • Go記憶體管理架構(本篇內容)

    • mcache
    • mcentral
    • mheap
  • 為什麼執行緒快取mcache是被邏輯處理器p持有,而不是系統執行緒m?
  • Go記憶體管理單元mspan

    • page的概念
    • mspan的概念
    • object的概念
    • sizeclass的概念
    • spanclass的概念
    • heaparena的概念
    • chunk的概念
  • Go堆記憶體的分配

    • 微物件分配
    • 小物件分配
    • 大物件分配
  • Go棧記憶體的分配

    • 棧記憶體分配時機
    • 小於32KB的棧分配
    • 大於等於32KB的棧分配

Go記憶體管理架構

Go的記憶體統一由記憶體管理器管理的,Go的記憶體管理器是基於Google自身開源的TCMalloc記憶體分配器為理念設計和實現的,關於TCMalloc記憶體分配器的詳細介紹可以檢視之前的文章。

先來簡單回顧下TCMalloc記憶體分配器的核心設計。

回顧TCMalloc記憶體分配器

TCMalloc誕生的背景?

在多核以及超執行緒時代的今天,多執行緒技術已經被廣泛運用到了各個程式語言中。當使用多執行緒技術時,由於多執行緒共享記憶體,執行緒申在請記憶體(虛擬記憶體)時,由於並行問題會產生競爭不安全。

為了保證分配記憶體的過程足夠安全,所以需要在記憶體分配的過程中加鎖,加鎖過程會帶來阻塞影響效能。之後就誕生了TCMalloc記憶體分配器並被開源。

TCMalloc如何解決這個問題?

TCMalloc全稱Thread Cache Memory alloc執行緒快取記憶體分配器。顧名思義就是給執行緒新增記憶體快取,減少競爭從而提高效能,當執行緒記憶體不足時才會加鎖去共享的記憶體中獲取記憶體。

接著我們來看看TCMalloc的架構。

TCMalloc的架構?

TCMalloc三層邏輯架構

  • ThreadCache:執行緒快取
  • CentralFreeList(CentralCache):中央快取
  • PageHeap:堆記憶體
TCMalloc架構上不同的層是如何協作的?

TCMalloc把申請的記憶體物件按大小分為了兩類:

  • 小物件 <= 256 KB
  • 大物件 > 256 KB

我們這裡以分配小物件為例,當給小物件分配記憶體時:

  • 先去執行緒快取ThreadCache中分配
  • 當執行緒快取ThreadCache的記憶體不足時,從對應SizeClass的中央快取CentralFreeList獲取
  • 最後,再從對應SizeClassPageHeap中分配

https://cdn.tigerb.cn/20210120132244.png

Go記憶體分配器的邏輯架構

採用了和TCMalloc記憶體分配器一樣的三層邏輯架構:

  • mcache:執行緒快取
  • mcentral:中央快取
  • mheap:堆記憶體

<p align="center">
<img src="http://cdn.tigerb.cn/20220405133623.png" style="width:60%">
</p>

實際中央快取central是一個由136個mcentral型別元素的陣列構成。

除此之外需要特別注意的地方:mcache被邏輯處理器p持有,而並不是被真正的系統執行緒m持有。(這個設計很有意思,後續會有一篇文章來解釋這個問題)

我們更新下架構圖如下:

http://cdn.tigerb.cn/20220405224809.png

「Go記憶體分配器」把申請的記憶體物件按大小分為了三類:

  • 微物件 0 < Micro Object < 16B
  • 小物件 16B =< Small Object <= 32KB
  • 大物件 32KB < Large Object

為了清晰看出這三層的關係,這裡以堆上分配小物件為例:

  • 先去執行緒快取mcache中分配記憶體
  • 找不到時,再去中央快取central中分配記憶體
  • 最後直接去堆上mheap分配一塊記憶體

<p align="center">
<img src="http://cdn.tigerb.cn/20220405224348.png" style="width:80%">
</p>

架構總結

通過以上的分析可以看出Go記憶體分配器的設計和開源TCMalloc記憶體分配器的理念、思路基本一致。對比圖如下:

http://cdn.tigerb.cn/20220405225026.png

最後我們總結下:

  • Go記憶體分配器採用了和TCMalloc一樣的三層架構。邏輯上為:

    • mcache:執行緒快取
    • mcentral:中央快取
    • mheap:堆記憶體
  • 執行緒快取mcache是被邏輯處理器p持有,而不是系統執行緒m

檢視《Go語言輕鬆進階》系列更多內容

連結 http://tigerb.cn/go/#/kernal/

相關文章