Memory

如何使核心壓縮碎片記憶體

  • February 8, 2018

我正在跑步Fedora 26

這是我的算法教授給出的一個非常奇怪的任務。作業說:

C 中的記憶體碎片:

設計、實現和執行一個 C 程序,它執行以下操作: 它為3m每個大小為 800,000 個元素的數組序列分配記憶體;然後它顯式地釋放所有偶數數組並分配一個數組序列,m每個數組大小為 900,000 個元素。測量程序分配第一個序列和第二個序列所需的時間量。選擇m耗盡程序可用的幾乎所有主記憶體。”

這樣做的總體目標是對記憶體進行分段,然後請求比作為連續塊可用的稍多的記憶體,從而迫使作業系統壓縮或整理記憶體。

在課堂上,我問我們應該怎麼做,因為記憶是視覺化的,而不是實際上是連續的,他回答說:“好吧,你必須轉向

$$ virtual memory $$其他同學在課堂上問我們怎麼知道什麼時候碰到了這個“垃圾回收”,他說:“第二次分配的時間應該比第一次大,因為垃圾回收需要時間” 在搜尋了一下之後,我能找到的最接近禁用虛擬記憶體的方法是使用swapoff -a. 我禁用了我的桌面環境,並從本機終端編譯並執行了我的程序(以避免可能受到其他程序的干擾,尤其是像桌面環境這樣的繁重程序)。我這樣做並以遞增的方式執行我的程序,m直到我達到第二次分配的時間大於第一次的時間點。

我以增加的方式執行程序,m最終發現第二次分配的時間超過了第一次分配的時間。然而,在此過程中,我遇到了在第二次分配之前程序被終止的地步。我檢查了一下dmesg,發現它是被oom-killer殺死的。我發現並閱讀了幾篇關於oom-killer 的文章,發現您可以禁用核心對記憶體的過度分配。

我這樣做並再次執行我的程序,只是這次我無法找到m第二個的時間高於第一個的時間。最終隨著 m 越來越大(儘管比啟用過度分配時小得多),malloc 將失敗並且我的程序將終止。

我有三個問題,其中第一個並不那麼重要:

  1. 垃圾收集是正確的術語嗎?我的教授非常堅決地說這是垃圾收集,但我假設垃圾收集是由程式語言完成的,這將被認為是更多的碎片整理。
  2. 在 linux 系統上可以實現他想要的壓縮嗎?
  3. 為什麼當我禁用交換但仍然啟用記憶體過度分配時,我能夠達到第二次分配的時間高於第一次的時間?壓實真的發生了嗎?如果是這樣,為什麼在禁用記憶體過度分配後我無法達到壓縮發生的地步?

感謝您迄今為止的研究,這確實是一組有趣的問題。

這里通常需要考慮一個重要方面:記憶體分配部分是作業系統的責任,部分是每個正在執行的程序的責任(忽略沒有記憶體保護和虛擬地址空間的舊系統)。作業系統負責為每個程序提供自己的地址空間,並在必要時為程序分配物理記憶體。每個程序都負責(在某種程度上)劃分其地址空間並確保它被適當地使用。請注意,程序的職責對程序員來說很大程度上是不可見的,因為執行時環境負責大多數事情。

現在,回答你的問題…

  1. 在我看來,垃圾收集是您在這裡所做的事情的一步。我想你正在用 C 語言編寫,使用malloc()and free()垃圾收集,在程式語言執行時環境支持的情況下,負責後一部分:它辨識以前分配但不再使用的記憶體塊(重要的是,永遠不能再次使用),並返回它們給分配器。JdeBP評論中連結的問題提供了一些背景知識,但我覺得它最有趣,因為它表明不同的人對垃圾收集有非常不同的看法,甚至是什麼構成垃圾收集

在我們感興趣的上下文中,我將使用“記憶體壓縮”來討論正在討論的過程。 2. 從使用者空間程式的角度來看,您的教授所要求的在 Linux 下的 C 語言中是不可能的,原因很簡單:我們在這里關心的不是物理記憶體碎片,而是地址空間碎片。當您分配許多 800,000 字節的塊時,您最終會得到同樣多的指向每個塊的指針。在 Linux 上,此時,作業系統本身並沒有做太多事情,並且您不一定有物理記憶體支持每個分配(順便說一句,分配較小的作業系統根本不會參與,只是您的C 庫的分配器;但這裡的分配足夠大,C 庫將使用mmap,由核心處理)。當您釋放奇數塊時,您將取回這些地址空間塊,但您無法更改指向其他塊的指針。如果你邊走邊列印指針,你會發現它們之間的區別不超過分配請求(我的系統上是 802,816 字節);對於 900,000 字節的塊,兩個指針之間沒有空間。因為您的程序具有指向每個塊的實際指針,而不是一些更抽象的值(在其他上下文中,是句柄),所以執行時環境對此無能為力,因此它無法壓縮其記憶體以合併空閒塊。

如果您使用的程式語言中指針不是程序員可見的概念,那麼在 Linux 下記憶體壓縮是可能的。另一種可能性是使用返回值不是指針的記憶體分配 API;例如,參見 Windows 下基於句柄的堆分配函式(其中指針僅在句柄被鎖定時才有效)。 3. 您的教授的練習有效地測量了 的性能mmap,其中包括其自由塊行走算法。你先分配 3× m個塊,然後釋放一半,然後再開始分配m個塊;釋放所有這些塊會在核心的分配器上轉儲大量空閒塊,它需要跟踪(free呼叫所花費的時間表明此時沒有進行優化)。如果您跟踪每個單獨塊的分配時間,您會發現第一個 900k 分配需要很多很多比其他的更長(我的系統上三個數量級),第二個更快但仍然需要更長的時間(兩個數量級),第三個分配回到典型的性能水平。所以發生了一些事情,但是返回的指針表明它不是記憶體壓縮,至少不是分配的塊壓縮(如上所述,這是不可能的) - 大概時間對應於處理核心使用的資料結構的時間跟踪程序中的可用地址空間(我正在檢查這一點,稍後會更新)。這些冗長的分配可能會使您正在測量的整體分配序列相形見絀,即 900k 分配最終花費的總時間比 800k 分配更長。

overcommit 改變你所看到的行為的原因是它改變了從純粹操作地址空間到實際分配記憶體的練習,從而減少了你的 Playground 的大小。當您可以過度使用時,核心僅受程序地址空間的限制,因此您可以分配更多的塊並對分配器施加更大的壓力。當您禁用過度使用時,核心會受到可用記憶體的限制,這會將您可以擁有的值m降低到分配器壓力不足以導致分配時間爆炸的級別。

引用自:https://unix.stackexchange.com/questions/422708