覺得這位同學關于記憶體動态配置設定講的很通俗易懂,是以轉載了:)
現象
1 壓力測試過程中,發現被測對象性能不夠理想,具體表現為:
程序的系統态CPU消耗20,使用者态CPU消耗10,系統idle大約70
2 用ps -o majflt,minflt -C program指令檢視,發現majflt每秒增量為0,而minflt每秒增量大于10000。
初步分析
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數值表示一個程序自啟動以來所發生的缺頁中斷的次數。
當一個程序發生缺頁中斷的時候,程序會陷入核心态,執行以下操作:
檢查要通路的虛拟位址是否合法
查找/配置設定一個實體頁
填充實體頁内容(讀取磁盤,或者直接置0,或者啥也不幹)
建立映射關系(虛拟位址到實體位址)
重新執行發生缺頁中斷的那條指令
如果第3步,需要讀取磁盤,那麼這次缺頁中斷就是majflt,否則就是minflt。
此程序minflt如此之高,一秒10000多次,不得不懷疑它跟程序核心态cpu消耗大有很大關系。
分析代碼
檢視代碼,發現是這麼寫的:一個請求來,用malloc配置設定2M記憶體,請求結束後free這塊記憶體。看日志,發現配置設定記憶體語句耗時10us,平均一條請求處理耗時1000us 。 原因已找到!
雖然配置設定記憶體語句的耗時在一條處理請求中耗時比重不大,但是這條語句嚴重影響了性能。要解釋清楚原因,需要先了解一下記憶體配置設定的原理。
記憶體配置設定的原理
從
作業系統角度來看,程序配置設定記憶體有兩種方式,分别由兩個系統調用完成:brk和mmap(不考慮共享記憶體)。brk是将資料段(.data)的最高位址指
針_edata往高位址推,mmap是在程序的虛拟位址空間中(一般是堆和棧中間)找一塊空閑的。這兩種方式配置設定的都是虛拟記憶體,沒有配置設定實體記憶體。在第
一次通路已配置設定的虛拟位址空間的時候,發生缺頁中斷,作業系統負責配置設定實體記憶體,然後建立虛拟記憶體和實體記憶體之間的映射關系。
在标準C庫中,提供了malloc/free函數配置設定釋放記憶體,這兩個函數底層是由brk,mmap,munmap這些系統調用實作的。
下面以一個例子來說明記憶體配置設定的原理:
1程序啟動的時候,其(虛拟)記憶體空間的初始布局如圖1所示。其中,mmap記憶體映射檔案是在堆和棧的中間(例如libc-2.2.93.so,其它資料檔案等),為了簡單起見,省略了記憶體映射檔案。_edata指針(glibc裡面定義)指向資料段的最高位址。
2
程序調用A=malloc(30K)以後,記憶體空間如圖2:malloc函數會調用brk系統調用,将_edata指針往高位址推30K,就完成虛拟記憶體
配置設定。你可能會問:隻要把_edata+30K就完成記憶體配置設定了?事實是這樣的,_edata+30K隻是完成虛拟位址的配置設定,A這塊記憶體現在還是沒有物
理頁與之對應的,等到程序第一次讀寫A這塊記憶體的時候,發生缺頁中斷,這個時候,核心才配置設定A這塊記憶體對應的實體頁。也就是說,如果用malloc配置設定了
A這塊内容,然後從來不通路它,那麼,A對應的實體頁是不會被配置設定的。
3程序調用B=malloc(40K)以後,記憶體空間如圖3.
4
程序調用C=malloc(200K)以後,記憶體空間如圖4:預設情況下,malloc函數配置設定記憶體,如果請求記憶體大于128K(可由
M_MMAP_THRESHOLD選項調節),那就不是去推_edata指針了,而是利用mmap系統調用,從堆和棧的中間配置設定一塊虛拟記憶體。這樣子做主
要是因為brk配置設定的記憶體需要等到高位址記憶體釋放以後才能釋放(例如,在B釋放之前,A是不可能釋放的),而mmap配置設定的記憶體可以單獨釋放。當然,還有
其它的好處,也有壞處,再具體下去,有興趣的同學可以去看glibc裡面malloc的代碼了。
5程序調用D=malloc(100K)以後,記憶體空間如圖5.
6程序調用free(C)以後,C對應的虛拟記憶體和實體記憶體一起釋放
7
程序調用free(B)以後,如圖7所示。B對應的虛拟記憶體和實體記憶體都沒有釋放,因為隻有一個_edata指針,如果往回推,那麼D這塊記憶體怎麼辦呢?
當然,B這塊記憶體,是可以重用的,如果這個時候再來一個40K的請求,那麼malloc很可能就把B這塊記憶體傳回回去了。
8程序調用free(D)以後,如圖8所示。B和D連接配接起來,變成一塊140K的空閑記憶體。
9預設情況下:當最高位址空間的空閑記憶體超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行記憶體緊縮操作(trim)。在上一個步驟free的時候,發現最高位址空閑記憶體超過128K,于是記憶體緊縮,變成圖9所示。
真相大白
說
完記憶體配置設定的原理,那麼被測子產品在核心态cpu消耗高的原因就很清楚了:每次請求來都malloc一塊2M的記憶體,預設情況下,malloc調用mmap
配置設定記憶體,請求結束的時候,調用munmap釋放記憶體。假設每個請求需要6個實體頁,那麼每個請求就會産生6個缺頁中斷,在2000的壓力下,每秒就産生
了10000多次缺頁中斷,這些缺頁中斷不需要讀取磁盤解決,是以叫做minflt;缺頁中斷在核心态執行,是以程序的核心态cpu消耗很大。缺頁中斷分
散在整個請求的處理過程中,是以表現為配置設定語句耗時(10us)相對于整條請求的處理時間(1000us)比重很小。
解決辦法
将動态記憶體改為靜态配置設定,或者啟動的時候,用malloc為每個線程配置設定,然後儲存在threaddata裡面。但是,由于這個子產品的特殊性,靜态配置設定,或者啟動時候配置設定都不可行。另外,Linux下預設棧的大小限制是10M,如果在棧上配置設定幾M的記憶體,有風險。
禁止malloc調用mmap配置設定記憶體,禁止記憶體緊縮。
在程序啟動時候,加入以下兩行代碼:
mallopt(M_MMAP_MAX, 0); // 禁止malloc調用mmap配置設定記憶體
mallopt(M_TRIM_THRESHOLD, -1); // 禁止記憶體緊縮
效果:加入這兩行代碼以後,用ps指令觀察,壓力穩定以後,majlt和minflt都為0。程序的系統态cpu從20降到10。
小結
可以用指令ps -o majflt minflt -C program來檢視程序的majflt, minflt的值,這兩個值都是累加值,從程序啟動開始累加。在對高性能要求的程式做壓力測試的時候,我們可以多關注一下這兩個值。
如果一個程序使用了mmap将很大的資料檔案映射到程序的虛拟位址空間,我們需要重點關注majflt的值,因為相比minflt,majflt對于性能的損害是緻命的,随機讀一次磁盤的耗時數量級在幾個毫秒,而minflt隻有在大量的時候才會對性能産生影響。