频繁分配释放内存导致的性能问题的分析--brk和mmap的实现
1 壓力測(cè)試過程中,發(fā)現(xiàn)被測(cè)對(duì)象性能不夠理想,具體表現(xiàn)為:?
進(jìn)程的系統(tǒng)態(tài)CPU消耗20,用戶態(tài)CPU消耗10,系統(tǒng)idle大約70?
2 用ps -o majflt,minflt -C program命令查看,發(fā)現(xiàn)majflt每秒增量為0,而minflt每秒增量大于10000。
初步分析
majflt代表major fault,中文名叫大錯(cuò)誤,minflt代表minor fault,中文名叫小錯(cuò)誤。
這兩個(gè)數(shù)值表示一個(gè)進(jìn)程自啟動(dòng)以來所發(fā)生的缺頁中斷的次數(shù)。
當(dāng)一個(gè)進(jìn)程發(fā)生缺頁中斷的時(shí)候,進(jìn)程會(huì)陷入內(nèi)核態(tài),執(zhí)行以下操作:?
檢查要訪問的虛擬地址是否合法?
查找/分配一個(gè)物理頁?
填充物理頁內(nèi)容(讀取磁盤,或者直接置0,或者啥也不干)?
建立映射關(guān)系(虛擬地址到物理地址)?
重新執(zhí)行發(fā)生缺頁中斷的那條指令?
如果第3步,需要讀取磁盤,那么這次缺頁中斷就是majflt,否則就是minflt。?
此進(jìn)程minflt如此之高,一秒10000多次,不得不懷疑它跟進(jìn)程內(nèi)核態(tài)cpu消耗大有很大關(guān)系。
分析代碼
查看代碼,發(fā)現(xiàn)是這么寫的:一個(gè)請(qǐng)求來,用malloc分配2M內(nèi)存,請(qǐng)求結(jié)束后free這塊內(nèi)存。看日志,發(fā)現(xiàn)分配內(nèi)存語句耗時(shí)10us,平均一條請(qǐng)求處理耗時(shí)1000us 。 原因已找到!?
雖然分配內(nèi)存語句的耗時(shí)在一條處理請(qǐng)求中耗時(shí)比重不大,但是這條語句嚴(yán)重影響了性能。要解釋清楚原因,需要先了解一下內(nèi)存分配的原理。?
內(nèi)存分配的原理
從操作系統(tǒng)角度來看,進(jìn)程分配內(nèi)存有兩種方式,分別由兩個(gè)系統(tǒng)調(diào)用完成:brk和mmap(不考慮共享內(nèi)存)。brk是將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址推,mmap是在進(jìn)程的虛擬地址空間中(一般是堆和棧中間)找一塊空閑的。這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第一次訪問已分配的虛擬地址空間的時(shí)候,發(fā)生缺頁中斷,操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。?
在標(biāo)準(zhǔn)C庫中,提供了malloc/free函數(shù)分配釋放內(nèi)存,這兩個(gè)函數(shù)底層是由brk,mmap,munmap這些系統(tǒng)調(diào)用實(shí)現(xiàn)的。?
下面以一個(gè)例子來說明內(nèi)存分配的原理:
1進(jìn)程啟動(dòng)的時(shí)候,其(虛擬)內(nèi)存空間的初始布局如圖1所示。其中,mmap內(nèi)存映射文件是在堆和棧的中間(例如libc-2.2.93.so,其它數(shù)據(jù)文件等),為了簡(jiǎn)單起見,省略了內(nèi)存映射文件。_edata指針(glibc里面定義)指向數(shù)據(jù)段的最高地址。?
2進(jìn)程調(diào)用A=malloc(30K)以后,內(nèi)存空間如圖2:malloc函數(shù)會(huì)調(diào)用brk系統(tǒng)調(diào)用,將_edata指針往高地址推30K,就完成虛擬內(nèi)存分配。你可能會(huì)問:只要把_edata+30K就完成內(nèi)存分配了?事實(shí)是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊內(nèi)存現(xiàn)在還是沒有物理頁與之對(duì)應(yīng)的,等到進(jìn)程第一次讀寫A這塊內(nèi)存的時(shí)候,發(fā)生缺頁中斷,這個(gè)時(shí)候,內(nèi)核才分配A這塊內(nèi)存對(duì)應(yīng)的物理頁。也就是說,如果用malloc分配了A這塊內(nèi)容,然后從來不訪問它,那么,A對(duì)應(yīng)的物理頁是不會(huì)被分配的。?
3進(jìn)程調(diào)用B=malloc(40K)以后,內(nèi)存空間如圖3.?
4進(jìn)程調(diào)用C=malloc(200K)以后,內(nèi)存空間如圖4:默認(rèn)情況下,malloc函數(shù)分配內(nèi)存,如果請(qǐng)求內(nèi)存大于128K(可由M_MMAP_THRESHOLD選項(xiàng)調(diào)節(jié)),那就不是去推_edata指針了,而是利用mmap系統(tǒng)調(diào)用,從堆和棧的中間分配一塊虛擬內(nèi)存。這樣子做主要是因?yàn)閎rk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的),而mmap分配的內(nèi)存可以單獨(dú)釋放。當(dāng)然,還有其它的好處,也有壞處,再具體下去,有興趣的同學(xué)可以去看glibc里面malloc的代碼了。?
5進(jìn)程調(diào)用D=malloc(100K)以后,內(nèi)存空間如圖5.?
6進(jìn)程調(diào)用free(C)以后,C對(duì)應(yīng)的虛擬內(nèi)存和物理內(nèi)存一起釋放?
7進(jìn)程調(diào)用free(B)以后,如圖7所示。B對(duì)應(yīng)的虛擬內(nèi)存和物理內(nèi)存都沒有釋放,因?yàn)橹挥幸粋€(gè)_edata指針,如果往回推,那么D這塊內(nèi)存怎么辦呢?當(dāng)然,B這塊內(nèi)存,是可以重用的,如果這個(gè)時(shí)候再來一個(gè)40K的請(qǐng)求,那么malloc很可能就把B這塊內(nèi)存返回回去了。?
8進(jìn)程調(diào)用free(D)以后,如圖8所示。B和D連接起來,變成一塊140K的空閑內(nèi)存。?
9默認(rèn)情況下:當(dāng)最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項(xiàng)調(diào)節(jié))時(shí),執(zhí)行內(nèi)存緊縮操作(trim)。在上一個(gè)步驟free的時(shí)候,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K,于是內(nèi)存緊縮,變成圖9所示。
真相大白
說完內(nèi)存分配的原理,那么被測(cè)模塊在內(nèi)核態(tài)cpu消耗高的原因就很清楚了:每次請(qǐng)求來都malloc一塊2M的內(nèi)存,默認(rèn)情況下,malloc調(diào)用mmap分配內(nèi)存,請(qǐng)求結(jié)束的時(shí)候,調(diào)用munmap釋放內(nèi)存。假設(shè)每個(gè)請(qǐng)求需要6個(gè)物理頁,那么每個(gè)請(qǐng)求就會(huì)產(chǎn)生6個(gè)缺頁中斷,在2000的壓力下,每秒就產(chǎn)生了10000多次缺頁中斷,這些缺頁中斷不需要讀取磁盤解決,所以叫做minflt;缺頁中斷在內(nèi)核態(tài)執(zhí)行,因此進(jìn)程的內(nèi)核態(tài)cpu消耗很大。缺頁中斷分散在整個(gè)請(qǐng)求的處理過程中,所以表現(xiàn)為分配語句耗時(shí)(10us)相對(duì)于整條請(qǐng)求的處理時(shí)間(1000us)比重很小。
解決辦法
將動(dòng)態(tài)內(nèi)存改為靜態(tài)分配,或者啟動(dòng)的時(shí)候,用malloc為每個(gè)線程分配,然后保存在threaddata里面。但是,由于這個(gè)模塊的特殊性,靜態(tài)分配,或者啟動(dòng)時(shí)候分配都不可行。另外,Linux下默認(rèn)棧的大小限制是10M,如果在棧上分配幾M的內(nèi)存,有風(fēng)險(xiǎn)。?
禁止malloc調(diào)用mmap分配內(nèi)存,禁止內(nèi)存緊縮。
在進(jìn)程啟動(dòng)時(shí)候,加入以下兩行代碼:
mallopt(M_MMAP_MAX, 0); // 禁止malloc調(diào)用mmap分配內(nèi)存
mallopt(M_TRIM_THRESHOLD, -1); // 禁止內(nèi)存緊縮
效果:加入這兩行代碼以后,用ps命令觀察,壓力穩(wěn)定以后,majlt和minflt都為0。進(jìn)程的系統(tǒng)態(tài)cpu從20降到10。
小結(jié)
可以用命令ps -o majflt minflt -C program來查看進(jìn)程的majflt, minflt的值,這兩個(gè)值都是累加值,從進(jìn)程啟動(dòng)開始累加。在對(duì)高性能要求的程序做壓力測(cè)試的時(shí)候,我們可以多關(guān)注一下這兩個(gè)值。?
如果一個(gè)進(jìn)程使用了mmap將很大的數(shù)據(jù)文件映射到進(jìn)程的虛擬地址空間,我們需要重點(diǎn)關(guān)注majflt的值,因?yàn)橄啾萴inflt,majflt對(duì)于性能的損害是致命的,隨機(jī)讀一次磁盤的耗時(shí)數(shù)量級(jí)在幾個(gè)毫秒,而minflt只有在大量的時(shí)候才會(huì)對(duì)性能產(chǎn)生影響。
原文地址:頻繁分配釋放內(nèi)存導(dǎo)致的性能問題的分析
http://bbs.csdn.net/topics/330179712
參考:
內(nèi)存分配的原理__進(jìn)程分配內(nèi)存有兩種方式,分別由兩個(gè)系統(tǒng)調(diào)用完成:brk和mmap(不考慮共享內(nèi)存)。
總結(jié)
以上是生活随笔為你收集整理的频繁分配释放内存导致的性能问题的分析--brk和mmap的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言的HashTable简单实现
- 下一篇: open的O_DIRECT选项