檢視原始碼 超級載體

超級載體是一個在 VM 啟動時配置的大型記憶體區域,可在執行期間用於配置一般的載體。

超級載體功能在 OTP R16B03 中引入。它透過命令列選項 +MMscs <以 Mb 為單位的尺寸> 啟用,並且可以使用其他選項進行配置。

問題

此功能的最初動機是客戶要求一種在 VM 啟動時預先配置實體記憶體供其使用的方法。

其他問題是作業系統 mmap 實作的不同體驗限制

  • 隨著 mmap 區域數量的增加,mmap/munmap 的效能越來越差。
  • mmap 區域之間的碎片問題。

第三個問題是半字元模擬器中的低記憶體管理。該實作使用簡單的線性搜尋結構來保存可用區段,當碎片增加時,這會導致效能不佳。

解決方案

在 VM 啟動時配置一個大型的連續位址空間區域,然後使用該區域來滿足執行期間的動態記憶體需求。換句話說:實作我們自己的 mmap。

使用案例

如果命令列選項 +MMscrpm (保留實體記憶體) 設定為 false,則只會從啟動時為超級載體配置虛擬空間。然後,超級載體充當「替代 mmap」實作,而不會改變實體記憶體頁面的消耗。當從超級載體完成配置時,會按需保留實體頁面,並且當記憶體釋放回超級載體時,會取消保留。

如果 +MMscrpm 設定為 true(預設值),則初始配置將保留整個超級載體的實體記憶體。這可以讓想要確保 VM 具有特定最低實體記憶體量的使用者使用。

然而,保留實體記憶體的實際含義高度取決於作業系統及其配置方式。例如,Linux 上不同的記憶體過度提交設定會大幅改變行為。

第三個功能是讓超級載體限制 VM 使用的最大記憶體量。如果 +MMsco (僅超級載體) 設定為 true(預設值),則配置將只從超級載體完成。當超級載體已滿時,VM 將因記憶體不足而失敗。如果 +MMsco 為 false,則當超級載體已滿時,配置將直接使用 mmap。

實作

整個超級載體實作都保留在 erl_mmap.c 中。這個名稱暗示它可以被視為我們自己的 mmap 實作。

超級載體需要滿足兩種略有不同的配置請求;多區塊載體 (MBC) 和單區塊載體 (SBC)。它們都是相當大的連續記憶體區塊,但是 MBC 和 SBC 對對齊和大小有不同的要求。

SBC 可以有任意大小,並且只需要最少 8 位元組對齊。

MBC 的限制較多。它們只能具有一些固定大小,這些大小是 2 的次方。起始位址需要具有非常大的對齊方式(目前為 256 kb,稱為「超級對齊」)。這是一種設計選擇,允許在 MBC 中每個配置的區塊具有非常低的額外負荷。

為了減少超級載體內的碎片,最好將 SBC 和 MBC 分開。具有統一對齊和大小的 MBC 可以非常有效地打包在一起。沒有對齊要求的 SBC 也可以非常有效地配置在一起。但是,當我們需要為下一個對齊限制建立大型填充孔時,將它們混合在一起可能會導致大量記憶體浪費。

因此,超級載體包含兩個區域。一個區域用於從底部向上增長的 MBC。一個區域用於從頂部向下增長的 SBC。就像一個堆積和堆疊朝彼此增長的進程一樣。

資料結構

MBC 區域稱為 sa,表示超級對齊,而 SBC 區域稱為 sua,表示超級未對齊。

請注意,超級對齊中的「超級」與超級載體中的「超級」無關。我們可以選擇其他命名來避免混淆,例如「中繼」載體或「巨大」對齊。

+-------+ <---- sua.top
|  sua  |
|       |
|-------| <---- sua.bot
|       |
|       |
|       |
|-------| <---- sa.top
|       |
|  sa   |
|       |
+-------+ <---- sa.bot

當載體被取消配置時,會在相應區域內建立一個可用記憶體區段,除非該載體位於最頂部(在 sa 中)或最底部(在 sua 中),在這種情況下,該區域只會向下或向上收縮。

我們需要追蹤所有可用區段,以便將它們重新用於新的載體配置。最初的一個想法是使用與追蹤 MBC 內可用區塊相同的機制(alloc_util 和不同的策略)。但是,這不會像人們想像的那麼簡單,並且還會浪費大量記憶體,因為它會使用前置區塊標頭。超級載體的粒度是一個記憶體頁面(通常為 4kb)。我們想要配置和釋放整個頁面,並且我們不想僅僅為了保存後續頁面的區塊標頭而浪費整個頁面。

相反,我們將有關所有可用區段的中繼資訊儲存在一個與 sasua 區域分開的專用區域中。每個可用區段都由一個描述符結構 (ErtsFreeSegDesc) 表示。

typedef struct {
    RBTNode snode;      /* node in 'stree' */
    RBTNode anode;      /* node in 'atree' */
    char* start;
    char* end;
}ErtsFreeSegDesc;

為了找到滿足載體配置的最小可用區段(最佳擬合),可用區段會組織成一個按大小排序的樹狀結構 (stree)。我們會在配置時在這個樹狀結構中搜尋。如果找不到足夠大小的可用區段,則會擴展該區域 (sasua)。如果存在兩個或更多大小相等的可用區段,則會選擇位址最低的區段用於 sa,而位址最高的區段用於 sua

在載體取消配置時,我們想要與任何相鄰的可用區段合併,以形成一個大型的可用區段。為此,所有可用區段也會組織成一個按位址順序排序的樹狀結構 (atree)。

因此,總共我們為超級載體保留了四個可用描述符樹狀結構;兩個用於 sa,兩個用於 sua。它們都使用相同的紅黑樹實作,支援使用的不同排序順序。

當配置新的 MBC 時,我們會先在 sa 中搜尋可用區段,然後嘗試提高 sa.top,然後作為後備嘗試在 sua 中搜尋可用區段。當在 sua 中配置 MBC 時,會配置一個較大的區段,然後將其修剪以獲得正確的對齊方式。SBC 的配置搜尋按相反的順序進行。當在 sa 中配置 SBC 時,大小會向上對齊為超級對齊的大小。

可用描述符區域

如上所述,可用區段的描述符是在一個單獨的區域中配置的。此區域具有恆定的可配置大小 (+MMscrfsd),預設為 65536 個描述符。這在大多數情況下應該足夠了。如果描述符區域填滿,則會首先直接從作業系統配置新的描述符區域,然後從超級載體的 suasa 中配置,最後從正在取消配置的記憶體區段本身中配置。從超級載體配置可用描述符區域只是最後的手段,應避免使用,因為這會產生碎片。

半字元模擬器

半字元模擬器使用超級載體實作來管理其所有詞語儲存所需的低記憶體對應。此處無法透過命令列選項配置超級載體。人們可以想像第二個可配置的超級載體實例,用於高記憶體配置,但尚未實作。