RFC-0219:Zircon Page 壓縮

RFC-0219:Zircon 頁面壓縮
狀態已接受
區域
  • 核心
說明

匿名 VMO 頁面的核心壓縮。

問題
變更
作者
審查人員
提交日期 (年/月)2022-05-31
審查日期 (年/月)2023-06-03

摘要

本文件建議為匿名使用者記憶體新增核心壓縮與解壓縮系統。

雖然系統是核心,但由於會影響記憶體會計,因此使用者空間無法完全公開。但除了 API 變更外,所有功能變更都包含在核心中。

提振精神

Fuchsia 經常用於資源受限的裝置,其中的記憶體集區越多,可大幅改善使用者體驗。

記憶體核心壓縮是其他作業系統採用的標準技術,支援此技術可協助 Fuchsia 保持一致。

相關人員

講師:

cpu@google.com

審查者:

eieio@google.com、rashaeqbal@google.com、maniscalco@google.com

顧問:

mseaborn@google.com、mvanotti@google.com、Wez@google.com、plabatut@google.com、jamesr@google.com、davemoore@google.com

社群媒體化:

與 Zircon 核心團隊和更廣大的利害關係人分享的核心設計與實作部分適用的 RFC 提案文件。

設計

本節將概述實作、壓縮策略,以及建議的 API 變更和擴充功能整體設計選擇。

老化與追蹤

匿名網頁的處理方式應與以 Pager 備份的頁面類似,只是這樣,這樣才會過時:

  • 在匿名網頁中加入反向連結,讓 vm_page_tVmObjectPaged + 偏移查詢。因此,每當頁面在 VMO 間移動時,重新連結也需增加成本。
  • 變更網頁佇列,避免以不同的方式處理呼叫器和匿名網頁。

在網頁佇列中,已有有效和已停用集的概念。現有的移除機制絕不會從有效集中移除,以免發生輾轉狀況。匿名網頁會歸入同一個佇列,且壓縮限制相同。

系統會將壓縮後的網頁從網頁佇列追蹤中移除,就像移除的網頁一樣。系統會將無法壓縮的網頁移至個別清單,這樣系統就不會再次嘗試壓縮。存取這類網頁之後,該網頁會移回使用中的集合,列出可能的年齡,並嘗試再次進行壓縮。

雖然我們只會壓縮已停用的網頁,但支援以下三種觸發事件:

  • 處理 LRU 佇列時,也會不斷壓縮舊網頁,即使沒有記憶體壓力也不受影響。
  • 記憶體壓力導致壓縮,搭配執行剔除。
  • 壓縮以避免 OOM。

這些觸發條件會透過核心 cmdline 標記切換。

VmPageList 標記

VmPageList 商店需要具備以下類型的標記:

  • 表示網頁存在,但已經過壓縮。
  • 儲存足夠的中繼資料,以便在壓縮儲存空間中找出網頁。

VmPageList 的項目已編碼為儲存 vm_page_t 指標和零網頁標記,且可延伸以進行額外的編碼。

目前網頁清單項目為 64 位元值,該值已編碼為下列選項:

  • 0:項目空白
  • 最適切的位元 = 1:零網頁標記
  • 其他:指向 vm_page_t 的指標

此配置的運作方式是由於指標對齊,vm_page_t 指標的底部 3 位元一律為零。因此,我們可以一般化此配置,為頁面清單項目的最後 3 位元代表類型,其餘位元則是相關聯的資料。初始類型如下:

  • 0:空白項目或 vm_page_t 指標 (視已設定資料位元而定)
  • 1:沒有網頁標記。其餘資料位元數必須為 0。
  • 2:壓縮頁面。剩餘位元是壓縮系統傳回的不透明權杖。
  • 3-7:目前時間類型無效。

壓縮運算

為了專注於簡單有效的初始用途,初始儲存空間策略將執行單一網頁壓縮和解壓縮作業,而不會依 VMO 進行明確分組。

如果不依 VMO 將頁面分組,壓縮壓縮路徑的方式就與現有的移除系統相同。雖然來自不同 VMO 的頁面實際上會在單一儲存系統中有效運作,但對於不會在不同 VMO 之間建立依附元件而言,這是嚴格要求。這麼做不僅可以防止資訊透過時間管道外洩,也能避免長時間延遲不相關的程序。

為避免不必要的鎖定爭用,建議在不按住 VMO 鎖定的情況下執行壓縮或解壓縮演算法,而且在保留解壓縮所需的任何鎖定時,不應進行壓縮。

壓縮演算法

以下概略說明壓縮路徑,其中補充了錯誤處理案例。壓縮的輸入內容是頁面、其擁有的 VMO,以及該頁面在 VMO 中的偏移。偏移量可用來驗證在取得 VMO 鎖定後,頁面是否仍位於 VMO 中,即發生此虛擬程式碼略過的錯誤處理時。

Take VMO lock
Update page list to use temporary compression reference
Release VMO lock
Compress page to buffer
Take compressed storage lock
Allocate storage slot
Release storage lock
Copy buffer to storage
Take VMO lock
Check page list still holds temporary compression reference
Update page list to final compression reference
Release VMO lock

一開始使用臨時參考資料的動機,而不是立即分配參照,而是要提供更高的彈性,讓壓縮儲存系統產生參照,藉由提供在產生參照時會儲存的資料大小。這樣一來,它就能在參照中呈現資料的確切位置,而不必另外透過額外的資料表間接。

即使未使用臨時參照,仍需取得兩次 VMO 鎖定,才能解決在壓縮期間使用頁面的任何潛在競爭問題。例如,頁面 (本案例參考資料) 在壓縮期間可能會解除,除非再次取得 VMO 鎖定並檢查,否則我們不會知道此頁面。

在壓縮期間,由於未保留 VMO 鎖定,其他執行緒可能會找到並嘗試使用臨時參照。由於臨時參照並沒有可解壓縮的資料,我們提供以下兩種解決方法:

  • 請等到壓縮完成後再擷取原始網頁。
  • 複製原始頁面 (壓縮執行作業開始時,會預先分配此頁面的記憶體)。

這麼做的目的在於確保網頁為自有且處於已知狀態,整個進行時間壓縮。壓縮期間使用原始網頁會造成問題,原因如下:

  • 在壓縮演算法讀取時修改頁面,會增加壓縮演算法的潛在錯誤和受攻擊面,因為這樣可能無法有效因應並行資料變更。
  • VMO 的快取能力或其他屬性可能已變更,導致壓縮器未定義的行為使平行快取用量。

這裡的提案就是複製原始網頁,因為發生這種狀況的機率不高,複製作業可更快速地解決要求,且不必使用其他同步處理機制。這裡的複製作業是在 VMO 鎖定底下進行,不過這與其他寫入時複製路徑相同,您可以在 VMO 鎖定下方執行頁面複製作業。由於一次只會進行單一壓縮,任何特定 VMO 作業都最多能執行一次此情境。

解壓縮演算法

解壓縮作業是為了利用現有 PageRequest 系統,該系統已用於延遲配置及執行使用者呼叫器要求。使用者呼叫器 VMO 缺少頁面的方式類似,可在 VMO 頁面清單中產生經過壓縮的參照,也會觸發類似的程序:

Take VMO lock
Observe page is compressed
Fill in the PageRequest
Drop VMO lock
Wait on the PageRequest
Retry operation

填入頁面要求時,我們必須從頁面清單儲存壓縮參照、VMO 和位移。這需要擴充現有的 PageRequest 結構。

使用者呼叫器頁面要求和壓縮的差別在於,等待的動作可以直接解析要求,在這種情況下,也就是執行解壓縮。

解壓縮作業需要容許多個嘗試存取相同頁面的執行緒的容忍情況,並能為資料流提供優先順序的繼承方式:

Declare stack allocated OwnedWaitQueue
Take compressed storage lock
Validate compressed reference
if compressed page has wait queue then
    take thread_lock
    release compressed storage lock
    wait on referenced wait queue
else
    store reference to our allocated wait queue in compressed block
    release compressed storage lock
    decompress into new page
    take VMO lock
    update VMO book keeping
    release VMO lock
    take compressed storage lock
    take thread lock
    remove wait queue reference
    release compressed storage lock
    wake all threads on wait queue

這裡只使用 thread_lock,因為這是執行等待佇列作業需要保留的鎖定,而且沒有使用 thread_lock 的其他屬性。

目標壓縮大小

由於解壓縮相關的成本,只有在網頁可以有足夠的大小縮減的情況下,才值得儲存已壓縮版本的網頁。否則系統可能會一併儲存未壓縮的版本,藉此節省解壓縮費用。

這個目標大小應該是可調整的選項,但其他系統也很常見 70% 的目標。

凡是不符合這個目標的壓縮嘗試,都會視為已失敗,且會按照搜尋和追蹤部分所述,將網頁放回網頁佇列中。

不掃描頁面

從根本來說,壓縮功能需要檢查來源頁面中的所有位元組,因此可偵測並刪除重複的零網頁。這表示壓縮的另一種成功結果,其中回報輸入頁面只是零頁面,不會將結果儲存在壓縮儲存系統中。VMO 呼叫端可以將網頁取代為零頁面標記。

分支壓縮演算法實作作業通常無法回報輸入內容皆為零,因此需要下列其中一項做法:

  • 修改或建立導入方式來執行這項追蹤。
  • 比較壓縮後的結果與已知簽章。

第二個選項運用壓縮演算法具有確定性,通常在數十個位元組內通常沒有任何頁面表示法。先執行大小檢查,再進行記憶體比較檢查,因此成本可能足以負擔。

本提案不必進行初始壓縮實作,也能執行零網頁偵測作業,但必須建構 API 和 VMO 邏輯來支援這項功能。最終,您應替換現有的零頁面掃描工具,以透過壓縮來支援此功能。

壓縮演算法

我們提議採用 LZ4 做為初始壓縮演算法。詳情請參閱實驗評估一節,但必須符合以下重要要求:

  • 對小型大小 (4KiB) 來說成效良好。
  • 在穩定的發布後初始化作業期間,不會執行 malloc 或對等項目。
  • 可在沒有共用可變動狀態的情況下平行解壓縮。壓縮必須能夠同時進行才能解壓縮。
  • 雖然這不是必要步驟,但系統本身支援多個平行壓縮功能。

LZ4 也有額外的實用屬性,可以根據受限制的目標緩衝區大小提前取消壓縮。

壓縮後的儲存空間

將經過壓縮的網頁儲存起來,在程式碼複雜度與執行時間之間取捨,可以避免資料片段化,並增加記憶體用量。初始的策略是將固定數量的壓縮頁面封裝成單一儲存空間頁面 (如本例為三)。

這個儲存空間策略的核心概念,是每個儲存空間頁面都有左側、中間和右側儲存空間運算單元,因此可達到最佳的 3:1 壓縮比率。

而且導入程序相當簡單,涉及:

  • 搜尋任何已填滿版位的現有網頁。
  • 如果沒有合適的現有版位,請放在新頁面的左側版位。

其中兩個小工具如下:

  • 部分填寫完成的網頁應該根據其免費運算單元大小,歸類到搜尋清單,以達到透過 O(1) 搜尋達到最佳封裝效果。
  • 如果中間運算單元中有足夠資料,則中間運算單元可能會無法使用。

這種策略的優點如下:

  • 導入方式輕鬆簡單。
  • 沒有長尾效能的可預測作業。
  • 在最糟的情況下降低,而不使用任何額外的記憶體。

可能需要處理的途徑行為是頁面在未壓縮後,可能會有缺漏填充的問題,用來釋出其他儲存空間頁面。如有需要,也可以實作這種壓縮系統。

長期來看,這可以發展為使用或更一般的堆積或實驗室樣式配置器。

請參閱實驗的評估作業部分,瞭解一些初步結果。

會計與指標

雖然壓縮作業是在使用者輸入內容的情況下執行,但會影響系統回報記憶體用量的方式。我們也想竊取使用者空間的其他資訊,以瞭解壓縮效能。

內容與承諾的比較

現有查詢介面是指已修訂的位元組和頁面,也就是直接在 VMO 中直接分配的記憶體,來存放資料。

這些現有的查詢如下:

  • ZX_INFO_TASK_STATS 會回報 mem_private_bytesmem_shared_bytes 等格式的記憶體。
  • ZX_INFO_PROCESS_MAPS,系統會回報對應 VMO 的 committed_pages
  • 回報 committed_bytesZX_INFO_PROCESS_VMOS / ZX_INFO_VMO
  • 回報 vmo_bytesZX_INFO_KMEM_STATS / ZX_INFO_KMEM_STATS_EXTENDED,視為已修訂。

除了修訂內容之外,我們也會新增其他內容位元組/頁面的概念。與已承諾記憶體相關的任何查詢都會繼續適用,具體記憶體中會直接保留 VMO 中的內容。內容指的是核心為使用者保存的資料量,而不具體說明相關做法。也就是說,內容可直接儲存在頁面中,因此也會計入修訂中,或經過壓縮,也不會列入提交內容中。

在此規模下,經過壓縮的內容經認定的儲存空間大小不明。嘗試為儲存空間中的個別壓縮頁面進行歸因作業,會相當複雜且不穩定,因為儲存空間大小取決於總系統壓縮行為,以及產生的片段。

雖然匿名 VMO 一開始即視為零,但這並不被視為核心會記住的內容。如果使用者明確修訂或修改任何頁面,就會視為內容。之後,即便使用者離開或將內容重設為零,核心就沒有義務注意到這一點,且可能會繼續將其視為追蹤的內容。

就移除功能和使用者呼叫器而言,可從使用者呼叫器擷取的內容不視為核心追蹤的內容。因此,系統會從修訂內容和內容金額中減去,

指標

為瞭解壓縮作業的成效 (包括調整 / 開發及持續監控系統健康狀態,我們需要收集並提供指標)。

這些指標應該能回答壓縮效能,以及壓縮對系統效能的影響。具體問題包括:

  • 壓縮儲存空間比率。
  • 網頁壓縮時長。
  • 哪些網頁會壓縮及觸發。
  • 解壓縮延遲時間。
  • 執行壓縮和解壓縮所用的 CPU 作業時間。
  • 壓縮成功與失敗壓縮時的比率。

API 變更

透過結構體演進和查詢版本管理來擴充:

  • zx_info_maps_mapping_t 即可包含「content_pages」欄位。
  • zx_info_vmo_t 即可包含「content_bytes」欄位。
  • ZX_INFO_TASK_RUNTIME 會將 page_fault_decompress_time 回報為總 page_fault 時間的子集。

需要為下列項目進行版本管理的 zx_object_get_info 查詢:

  • ZX_INFO_PROCESS_MAPS
  • ZX_INFO_PROCESS_VMOS
  • ZX_INFO_VMO
  • ZX_INFO_TASK_RUNTIME

zx_info_vmo_tcommitted_bytes 等現有欄位將更新註解和說明文件,明確指出這些欄位不會計算壓縮狀態的網頁。

完全相符的內容會按疊代階段進行,但建議新增 ZX_INFO_KMEM_STATS_COMPRESSION 查詢來回報:

struct zx_info_kmem_stats_compression {
    // Size in bytes of the content that is current being compressed.
    uint64_t uncompressed_content_bytes;

    // Size in bytes of all memory, including metadata, fragmentation and other
    // overheads, of the compressed memory area. Note that due to base book
    // keeping overhead this could be non-zero, even when
    // |uncompressed_content_bytes| is zero.
    uint64_t compressed_storage_bytes;

    // Size in bytes of any fragmentation in the compressed memory area.
    uint64_t compressed_fragmentation_bytes;

    // Total amount of time compression has spent on a CPU across all threads.
    // Compression may happen in parallel and so this can increase faster than
    // wall clock time.
    zx_duration_t compression_time;

    // Total amount of time decompression has spent on a CPU across all threads.
    // Decompression may happen in parallel and so this can increase faster than
    // wall clock time.
    zx_duration_t decompression_time;

    // Total number of times compression has been done on a page, regardless of
    // whether the compressed result was ultimately retained.
    uint64_t total_page_compression_attempts;

    // How many of the total compression attempts were considered failed and
    // were not stored. An example reason for failure would be a page not being
    // compressed sufficiently to be considered worth storing.
    uint64_t failed_page_compression_attempts;

    // Number of times pages have been decompressed.
    uint64_t total_page_decompressions;

    // Number of times a page was removed from storage without needing to be
    // decompressed. An example that would cause this is a VMO being destroyed.
    uint64_t compressed_page_evictions;

    // How many pages compressed due to the page being inactive, but without
    // there being memory pressure.
    uint64_t eager_page_compressions;

    // How many pages compressed due to general memory pressure.
    uint64_t memory_pressure_page_compressions;

    // How many pages compressed due to attempting to avoid OOM or near OOM
    // scenarios.
    uint64_t critical_memory_page_compressions;

    // The nanoseconds in the base unit of time for
    // |pages_decompressed_within_log_time|.
    uint64_t pages_decompressed_unit_ns;

    // How long pages spent compressed before being decompressed, grouped in log
    // buckets. Pages that got evicted, and hence were not decompressed, are not
    // counted here. Buckets are in |pages_decompressed_unit_ns| and round up
    // such that:
    // 0: Pages decompressed in <1 unit
    // 1: Pages decompressed between 1 and 2 units
    // 2: Pages decompressed between 2 and 4 units
    // ...
    // 7: Pages decompressed between 64 and 128 units
    // How many pages are held compressed for longer than 128 units can be
    // inferred by subtracting from |total_page_decompressions|.
    uint64_t pages_decompressed_within_log_time[8];
};

回報頁面經過壓縮的時間長度,意味著系統必須為每個壓縮的頁面記錄時間戳記。這應該不會對潛在壓縮率造成重大影響,以便能知道頁面是否快速解壓縮。

ZX_INFO_KMEM_STATS_EXTENDED 呼叫器位元組

ZX_INFO_KMEM_STATS_EXTENDED 查詢具有專門用於處理分頁器支援的 VMO 位元組數的欄位。這些欄位分別是:

    // The amount of memory committed to pager-backed VMOs.
    uint64_t vmo_pager_total_bytes;

    // The amount of memory committed to pager-backed VMOs, that has been most
    // recently accessed, and would not be eligible for eviction by the kernel
    // under memory pressure.
    uint64_t vmo_pager_newest_bytes;

    // The amount of memory committed to pager-backed VMOs, that has been least
    // recently accessed, and would be the first to be evicted by the kernel
    // under memory pressure.
    uint64_t vmo_pager_oldest_bytes;

提議的實作方式會整合網頁佇列中支援 Pager 和匿名的 VMO 頁面,這會導致這項資訊不切實際。因此,本提案將會重新定義這些記憶體,涵蓋所有可分頁 / 可收回的記憶體,而不只是明確可撤銷使用者呼叫器支援的 VMO。否則,系統會保留總數的定義,以及最新和最舊的定義。

雖然可壓縮記憶體的回收作業不會直接轉譯在 PMM 可用記憶體增加,但這些查詢並不代表可收回的 PMM 記憶體量。而是提供各年齡層記憶體相對分佈的深入分析,並可用來執行驗證,例如 oldest_bytes 在裝置 OOM 的下降且接近零等。

此外,如果有多個欄位為使用者呼叫分頁伺服器支援的可收回記憶體和可壓縮的匿名記憶體,也是令人困擾的值。無論如何,都能區分這些事件很重要,但大多數情況下,我都認為兩者的匯總報表也是一樣。

停用壓縮 / 易受延遲影響的 VMO

對於容易受到延遲影響的應用程式,必須設定一個機制來停用 VMO 壓縮功能,無論解壓延遲時間的最小程度為何,都可能超出其容忍度的能力。

避免回收和其他類型的核心活動可能會增加記憶體存取的延遲時間,這是現有的問題解決方法。因此,此處不會提出任何設計建議,但這項工作是壓縮的依附元件。

擴充功能

除了這項最初的建議設計之外,您也可以進行一些探索與改善。雖然這些實作細節通常屬於實作細節,但我們為了提昇此設計長期適合性。絕對是在進行產品使用前絕對應進行的改善,建議如下:

  • 移除個別的零網頁重複作業,改用壓縮功能。

您也可以選擇不多嘗試,看看可能帶來哪些實用優勢:

  • 請先壓縮呼叫器備份的記憶體,再將其收回。
  • 支援執行壓縮作業,而不必捨棄未壓縮的頁面,直到記憶體壓力為止。
  • 移動其他舊頁面清單,並積極壓縮 LRU 佇列中的網頁。

您亦可進一步調整壓縮的儲存空間和壓縮演算法,以直接演進或為不同產品提供選項。這些 RFC 可能會需要個別的 RFC,具體取決於侵入程度或差異。

實作

導入程序將針對本文列出的多個階段執行。

常見鷹架

我們會先實作用於支援老化和追蹤匿名頁面的常見 VM 系統變更。這些雖然沒有行為或 API 變更,但風險最高的變更項目是優先考量的事項:

  • 將變更分成樹狀結構,並測試盡可能在最長時間。
  • 能夠測量候選壓縮集區 (即舊的匿名頁面)。

核心實作

進行常見變更時,主要核心實作可置於功能旗標後方的樹狀結構中。如此一來,您就能在透過任何 API 公開壓縮的情況下,先行測試並測試壓縮實作,確保它保持穩定。

API 演進

針對指標和會計變更執行 API 演進和結構遷移作業。由於核心實作的結構是樹狀結構,雖然壓縮作業仍會保持功能旗標後方,因此除了一般使用者以外,一般使用者不會看到功能方面的變更。

整合、擴充功能及調整

實作測試完成後,您就能回報不同產品中的指標,藉此找出任何問題,進而執行擴充功能和調整作業。

這項測試的結果將決定以下事項:

  • 壓縮功能預設為開啟,部分產品會選擇不採用,或
  • 壓縮功能預設為關閉,部分產品會優先啟用。

擴充功能和調整

擴充設計的部分現在可降落,且一般實作也會持續調整。

效能

必須評估效能的兩個層面。

一般負擔

即使已停用壓縮功能,仍然需要進行一般 VM 程式碼的基本變更,並且會對效能造成影響。

將反向連結加到匿名頁面之後,所有 VMO 本機副本建立和刪除事件都會造成可量化的間接費用。這些作業並不在關鍵效能路徑上,因為這些作業通常會在初始化及拆解步驟期間發生,但應該檢查產品的任何影響。

其他 VMO 路徑會產生少量的負擔,以便維持反向連結和更新作業,但只有如此的資料才會納入雜訊,無法造成可評估的影響。

這些路徑在現有 VMO 微基準測試 (做為主要驗證工具) 的涵蓋範圍內。

壓縮效能

壓縮的核心在於 CPU 使用記憶體的取捨,因此能夠僅針對產品及其需求進行評估。

否則,建議使用的指標 API 能夠瞭解壓縮的耗用量,接著再使用壓縮可調整性控制這些成本,但處理與節省的記憶體量有關。

安全性考量

時間點管道

雖然壓縮的頁面可能有共置儲存空間,但存取此儲存空間的程序定義,在 VMO 上沒有任何遞移依附元件,導致可測量管道。這是刻意做到的,以避免可能與記憶體簡化攻擊類似的攻擊。

儘管缺乏遞移依附元件,但如果攻擊者能控制與密鑰共置在同一頁面的資料,系統可能仍會建立時間管道。這些資料可以製作成壓縮,並根據密鑰產生或失敗,讓攻擊者能藉此瞭解密鑰的個人資訊。不論這類攻擊實際上是否可行,如果疑慮與防止對延遲時間敏感的工作進行壓縮的機制相同,也可使用。

系統行為管道

整體系統記憶體用量可透過 ZX_INFO_KMEM_STATS_COMPRESSION 直接查詢,或透過查詢 VMO 等物件來推斷,然後觀察內容與修訂的位元組是否有差異。也可以查詢 ZX_INFO_KMEM_STATS_COMPRESSION 中的其他統計資料。

此資訊可提供整體系統行為的間接資訊。 然而,不知道您控管的 VMO 網頁是否經過壓縮,就足以瞭解其他程序的相關資訊。

LZ4

雖然 LZ4 程式庫的檔案數量很少,且包含 LoC 指標,但它是以低階 C 型編寫的複雜程式碼,並可能存在錯誤。程式庫需要下列項目:

  • 針對整體合適度進行安全性審查。
  • 其他強化和測試。
  • 可能會重新編寫有問題的內容。

強化及重新寫入可能會導致部分或所有實作移植到較安全的表示法,例如 Rust 或 WUFFS。

測試

與成效相似,您可以使用兩種維度進行測試。

VM 正確性

系統會同時進行單元測試和整合測試,評估常見 VM 變更與壓縮特定變更的技術正確性。

針對無論在核心單元測試是否已啟用壓縮,或未納入其他核心測試的情況下,適用於一般 VM 變更都會視情況新增。

在啟用壓縮的情況下,使用 QEMU 執行器的整合測試將用於執行其他測試。

系統行為

雖然壓縮不是免費的,但不應對產品擁有者視為重大的任何行為進行負面迴歸。這些行為是針對特定產品,但例如在記憶體不足的情況下略過音訊,或發出誇大的衝刺或使用者體驗不佳。

這包括手動測試產品、使用鎖定爭用追蹤等工具,以及與產品測試團隊和產品擁有者合作進行評估。

說明文件

新增和對 zx_object_get_info 查詢所做的新增和變更都會記錄下來,以及其他 cmdline 選項。

缺點、替代方案和未知

不明和疊代

您必須在實際產品妥善導入和評估這個提案之前,無法完整瞭解提案的實際優點和費用。雖然本提案致力為所有演算法和結構提供明確且合理的初始提案,但這些提案幾乎一定會根據實際的使用資料改變。

缺點

本提案的主要缺點是新增至核心中 VM 系統的複雜性。雖然只要停用就避免壓縮效能的方面,但通用基礎架構的存在,仍需要改變。因此,即使未使用,也會產生永久的複雜性和風險。

使用者呼叫器壓縮替代方式

與其在核心中執行壓縮和解壓縮,不如透過使用者呼叫器機制,將其外包給使用者空間。在許多方面這都類似於 Linux 上的 ZRAM,而 Linux 是由使用者空間檔案系統驅動程式庫實作。遺憾的是,它有許多相同的缺點。

好處

在使用者空間中進行壓縮的主要原因有二:

  1. 從核心中移除複雜的壓縮和解壓縮程式碼。
  2. 讓使用者彈性實作。

遺憾的是 (1) 不盡然,因為除非為每個安全性網域建立獨立的密封執行個體 (但實際上是如此),否則這個使用者空間程序可以查看系統中每個程序的匿名記憶體。這會讓攻擊者非常信任,因為任何遭入侵事件都能查看及編輯系統中的所有使用者記憶體。因此,不信任壓縮和解壓縮程式碼的長條會低於核心。

儘管提供實作的彈性固然值得,但如果有機制能夠將特定匿名 VMO 指派給特定壓縮器,可能會更有吸引力。這樣甚至可能不需要壓縮,但可以實作交換或任何其他策略。

缺點

使用者空間壓縮的主要缺點是它對核心提供的限制。能夠以樂觀的方式壓縮及解壓縮頁面 (也許可以捨棄未壓縮的網頁),可為核心帶來極大的實作彈性。您可以透過使用者呼叫器實作這類配置,但這種配置會比較複雜,需要極為謹慎的 API 和並行推理。

雖然 Zircon 是元件系統,為了切換使用者空間工作所花費的成本很低,但若需要終止使用者空間程序來解壓縮 4KiB 頁面,會大幅增加延遲時間和成本。這會加快壓縮速度,直到頁面較舊為止,進而降低可能節省的記憶體用量。對於現有的使用者呼叫器錯誤,我們假設查詢的是緩慢的永久儲存空間,因此清除結果已經轉換成盡可能延遲清除。

即使使用者空間會處理壓縮和解壓縮,但所有針對網頁年齡追蹤和觸發壓縮功能的核心變更,還是需要完成。因此所有可調整項目和指標仍須存在於核心中。此外,VM 系統目前無法將使用者分頁器與子 VMO 建立關聯,因此需要進一步變更並重新設計 VM 系統。

核心程式碼解譯器

與其使用固定的壓縮演算法,我們可以透過使用者提供的核心程式碼 (即 la Linux eBPF) 來支援使用者提供的演算法。這可讓您靈活執行使用者實作,而且不會因使用者向下呼叫而導致效能影響。

雖然不需要明確模式轉換,但核心仍需要以類似使用者向下呼叫的方式進行處理,因為效能特徵不明,因此在叫用提供的程式碼時仍須注意鎖定和其他依附元件。

這類方法的主要缺點是,核心中的 VM 必須有足夠的電力,可以產生可接受的壓縮效能,因此需要大量實作,確實會複雜且大幅增加核心的受信任運算基礎。

未來

未來,我們可能需要透過交換儲存空間支援匿名 VMO。這絕對需要透過使用者呼叫器機制,在使用者空間實作。此時,替換實作可以進行壓縮,而不只是替換而已。然而,由於壓縮的核心整合有益,所以我會看到這種與核心壓縮共存的做法,而不是加以取代。

會計替代方案

本提案目前建議對內容位元組/網頁定義新的定義,以伴隨現有的承諾使用數量。在不變更計算內容的情況下,還有其他命名選項,例如:

  • 居民和過去承諾
  • 居民和內容

其中每個部分都需要變更已承諾的所有現有使用情況、較大規模的改變,且會在兩個 API 版本之間建立起更中斷的現象。

ZX_INFO_KMEM_STATS_EXTENDED 替代呼叫位元組

雖然建議的實作會整合 Pager 和匿名佇列,導致無法輕易分隔計數,但其他實作方式可能會將計數分開。

這個替代實作會為每個可回收的佇列分別設定兩組不同的計數器。在 vm_page_t 中儲存的 page_queue_priv 中,高一些是用來判斷在佇列之間移動頁面時,需要更新哪些計數。

這種方法的缺點如下:

  • 增加 page_queue_priv 的使用方式的複雜度。這現在變成封裝欄位,而非計數器,且所有讀取和寫入作業都必須保留或遮蓋額外位元。
  • 存取的收集作業會發生在保留拱形空間鎖定的情況下,隨著佇列資訊需要解碼,並更新正確的計數,導致成本增加。
  • 多個數量欄位,並增加複雜度,讓這些欄位保持一致。

如提案所述,我想考量 Pager 位元組欄位實際使用哪些項目,因為實作的複雜性和執行階段成本會增加,所以每個計數的值都不需個別指定。

實驗評估

為了提出 LZ4 壓縮演算法和壓縮的儲存空間策略,我們建構了實驗版壓縮版本,以便執行以下作業:

  • 收集要壓縮的無效匿名網頁樣本資料集。
  • 對這個資料集執行不同的壓縮演算法。

這麼做的目的在於利用下列項目評估不同的壓縮演算法:

  • 來自實際產品的實際輸入資料。
  • 在受限制的執行環境中,且該環境符合核心。

第二點尤其重要,因為核心程式碼不支援 FPU/SSE/AVX 等。

評估作業使用 64MiB 輸入資料集執行,每個 4KiB 頁面都會個別提供給壓縮器,就像實際作業一樣。產生的資料會透過下列方式解讀:

  • 壓縮成功是指壓縮為原始大小的至少 70%。
  • 所有網頁都會計算壓縮時間,包括最終未壓縮的網頁。這符合之前無法得知的值的實際用法,無論壓縮是否成功。
  • 只有成功壓縮的網頁才會計算解壓縮時間。
  • 總大小會計入每個網頁所需的儲存空間,因此成功壓縮的網頁會將壓縮大小計入計數,而壓縮失敗的網頁則計為 4KiB。

這種計算樣式的理由,就是目標最終是為了釋出記憶體並使用最少 CPU。有時壓縮演算法會快速壓縮網頁,以致於無法壓縮大量 CPU,實際上並不會節省太多記憶體以及耗用大量 CPU。

NUC 7

演算法 壓縮 (MiB/秒) 解壓縮 (MiB/秒) 解壓縮最糟的延遲時間 (ns) 總大小 (MiB)
Zstd(-7) 752.1470396 1446.748532 9302.3125 18.61393929
Minilzo 1212.400326 2135.49407 6817.839844 16.16410065
Lz4(1) 1389.675715 2920.094092 6355.849609 17.21983242
Lz4(11) 1801.55426 3136.318174 5255.708984 19.92521095
WKdm 1986.560443 3119.523406 3095.283203 21.98047638

ARM A53

演算法 壓縮 (MiB/秒) 解壓縮 (MiB/秒) 解壓縮最糟的延遲時間 (ns) 總大小 (MiB)
Zstd(-7) 189.3189527 486.6744277 29856.93359 18.61393929
Minilzo 317.4546381 528.877112 15633.99219 16.16410065
Lz4(1) 294.5410547 1127.360347 12683.55273 17.21983242
Lz4(11) 378.2672263 1247.749072 12452.67676 19.92521095
WKdm 337.6087322 471.9796684 14254.39453 21.98047638

ARM A73

演算法 壓縮 (MiB/秒) 解壓縮 (MiB/秒) 解壓縮最糟的延遲時間 (ns) 總大小 (MiB)
Zstd(-7) 284.3621221 623.15235 20958.90332 18.61393929
Minilzo 487.4546545 2,399.9218419 16784.83105 16.16410065
Lz4(1) 441.5927551 1159.839867 10007.28418 17.21983242
Lz4(11) 573.7741984 1240.964187 9987.875 號 19.92521095
WKdm 639.5418085 597.6976995 13867.47266 21.98047638

壓縮後的儲存空間

相同的 64MiB 測試資料集也用於評估建議的壓縮儲存系統。在本例中,它使用兩種模式進行評估:兩個只有左側和右側版位的頁面系統,以及建議的三個運算單元系統。這麼做的用意是嘗試量化加入中間版位所帶來許多便利的好處,也就是真正簡單的兩個運算單元系統。

LZ4 是建議的壓縮演算法,用於產生要儲存的壓縮資料,並將加速係數設為 11。這表示在 16384 輸入頁面中,有 12510 個必須儲存且未壓縮產生的資料。

吃角子老虎 儲存空間頁面 總 MiB 節省成本 MiB 壓縮率
兩版位 6255 39.5 號 24.4 號 1,620
三版位 4276 31.8 號 32.1 2.01

請注意,6255 剛好是 12510 的一半,表示每個頁面都有一個可用耳機。也就是說,就 LZ4 輸入而言,6255 是最佳儲存策略結果。

先前的圖片和參考資料

所有主要的當代作業系統 (Windows、MacOS、Linux 和 Linux 衍生項目,例如 CastOS 等) 都支援將記憶體壓縮做為替代項目或取代磁碟的替代功能。

三頁儲存空間策略靈感來自 Linux zbud 和 z3fold 記憶體管理系統。