Google performance Tools (gperftools) 使用心得
gperftools是google開發的一款很是實用的工具集,主要包括:性能優異的malloc free內存分配器tcmalloc;基於tcmalloc的堆內存檢測和內存泄漏分析工具heap-profiler,heap-checker;基於tcmalloc實現的程序CPU性能監測工具cpu-profiler.
上述所說的三種工具在咱們服務器進程的性能分析監控,定位內存泄漏,尋找性能熱點,提升malloc free內存分配性能的各個方面上都有很是成功的使用經驗。
html
以前咱們一直使用glibc的malloc free,也是相安無事,後來就出現了內存泄漏的問題,服務器在出現一段高負載的狀況後,內存使用率很高,當負載降下來很長一段時間後,進程所佔用的內存仍然保持高佔用率下不來。一開始就肯定是內存泄漏,判斷是libevent收到的網絡包沒處理過來,在接收緩衝區越積越多,使得緩衝區佔用的內存也愈來愈大(由於以前就有這樣的經驗)。咱們開始來監控libevent的內存請況,由於libevent能夠替換malloc free(google libevent的event_set_mem_functions),咱們將默認的malloc free替換爲有日誌信息的本身定製的版本,而後再啓動服務器,分析結果。日誌顯示:在比較大壓力下,libevent使用的內存確實不少,基本符合top顯示出的內存使用狀況,但當壓力過去了,日誌明顯顯示libevent已經釋放掉了內存,但是top顯示的內存使用仍是很高。free掉的內存沒有還回給系統,並且過了一兩天也仍是現狀,咱們都知道malloc free是用了內存池的,可是把長時間沒使用的大塊內存還給系統,不是應該是內存池該作的事嗎? 既然這只是glibc的malloc free實現,也許他的內存分配算法就是這樣,可能有些其餘緣由也不必定,不瞭解其實現和原理,很差妄做評論。由於其實之前就有同事加上了tcmalloc,因此立刻想換換tcmalloc試試,這也是據說過tcmalloc是最快的malloc free實現,google內部許多工程都是使用它作底層雲雲。
換上tcmalloc很容易,根據須要以動態庫或靜態庫的形式連接進你的可執行程序中便可(gcc ... -o myprogram -ltcmalloc)。使用tcmalloc後按照上述的情形再跑了一次,發如今一樣的壓力下,cpu佔用率降低了10%~20%左右,甚是驚喜。可是在壓力過去很長一段時間後,一樣的問題出現了,內存佔用率仍是居高不下。這個很是困惑我,因而去網上去看官方文檔,看其實現原理和特性。而後就在在文檔發現了--把沒用的內存還回給系統原來是能夠控制的行爲。
如下徹底參考官方文檔http://google-perftools.googlecode.com/svn/trunk/doc/tcmalloc.html:
在tcmalloc中控制把未使用的內存還回給系統,主要是兩個方法,一種是修改環境變量TCMALLOC_RELEASE_RATE,這個值表明把未使用的內存還回給系統的速度吧,取值在0~10,值越高返還的速率越高,值爲0表示從不返還,默認是1.0,實際上是一個比較低的速率,因此在以前的表現中,就是很長一段時間內存降不下來。這個值的改變能夠在程序運行期間就起做用。這個環境變量也有對應的函數調用SetMemoryReleaseRate,能夠在程序代碼中調用。還有一種是一個函數調用:
MallocExtension::instance()->ReleaseFreeMemory();
這個函數如其名,把未使用的內存所有返還給系統。我按照第二種方法,讓運行着的服務器程序動態執行個人命令,這個命令就是運行上面的函數調用(咱們服務器有套機制,可以在運行時執行一些咱們自定義的命令,實現方式有不少,如信號),結果top上顯示的內存佔用率立馬降下來不少,頓時讓我以爲世界都清淨了。
後記:我估計glibc的malloc free也有相似控制,只不過現階段我並無時間和很大興趣去關注了。
linux
這兩個工具其實挺像的,heap-checker專門檢測內存泄漏,heap-profiler則是內存監控器,能夠隨時知道當前內存使用狀況(程序中內存使用熱點),固然也能檢測內存泄漏。咱們工做中一直是使用heap-profiler,實時監控程序的內存使用狀況。文檔在此:http://google-perftools.googlecode.com/svn/trunk/doc/heapprofile.html。
heap-profiler是基於tcmalloc的,正規開啓它的方法是在代碼中調用 HeapProfilerStart方法,參數是profile文件前綴名,相應的關閉則需調用 HeapProfilerStop。前面有介紹過咱們的服務器有運行時執行自定義命令的機制,咱們把兩個命令MemProfilerStart,MemProfilerStop實現成調用上述相應的開啓/關閉heap-profiler的API,這樣咱們能夠在服務器運行時,隨時開啓和關閉heap-profiler,很是方便的查看當前程序內存使用狀況。
heap-profiler會在每當必定量的內存被新申請分配出來時或者當一段固定時間過去後,輸出含有當前內存使用狀況的profile文件,文件名相似這種:
<prefix>.0000.heap
<prefix>.0001.heap
<prefix>.0002.heap
...
prefix是前文所說的profile數據文件的前綴,若是並無指定成絕對路徑則會在你的程序的工做目錄中生成,這些文件能夠被一個腳本工具pprof解析輸出成各類可視的數據格式文件,pprof使用了dot語言繪圖,須要安裝graphviz。pprof也是下文解析CPU profile文件的工具,pprof工具能夠把profile信息輸出成多種格式,如pdf,png,txt等,若是是以圖的形式顯示,則是根據調用堆棧的有向圖,像下圖這樣:
這個圖裏面每一個節點表明一個函數調用,好比GFS_MasterChunkTableUpdateState節點,176.2(17%) of 729.9(70%) 大體表示這個函數自己本身直接消耗了17%的內存,加上子調用共消耗了70%的內存,而後每條邊則顯示每一個子調用花費了多少內存。由於咱們的linux系統並未安裝圖形界面,一般都是直接生成了txt文件:
% pprof --text gfs_master /tmp/profile.0100.heap
255.6 24.7% 24.7% 255.6 24.7% GFS_MasterChunk::AddServer
184.6 17.8% 42.5% 298.8 28.8% GFS_MasterChunkTable::Create
176.2 17.0% 59.5% 729.9 70.5% GFS_MasterChunkTable::UpdateState
169.8 16.4% 75.9% 169.8 16.4% PendingClone::PendingClone
76.3 7.4% 83.3% 76.3 7.4% __default_alloc_template::_S_chunk_alloc
49.5 4.8% 88.0% 49.5 4.8% hashtable::resize
第一列表明這個函數調用自己直接使用了多少內存,第二列表示第一列的百分比,第三列是從第一行到當前行的全部第二列之和,第四列表示這個函數調用本身直接使用加上全部子調用使用的內存總和,第五列是第四列的百分比。基本上只要知道這些,就能很好的掌握每一時刻程序運行內存使用狀況了,而且對比不一樣時段的不一樣profile數據,能夠分析出內存走向,進而定位熱點和泄漏。
在咱們的實踐中,也常常發現一些環境變量很是有用:
HEAP_PROFILE_ALLOCATION_INTERVAL:上文說每當必定量的內存被新申請分配出來時,就會輸出profile文件,這個變量值就是控制多少字節,默認是(1024*1024*1024)1GB,粒度相對比較大,經常會被咱們調整爲幾百MB甚至更小。
HEAP_PROFILE_MMAP:有時候程序申請內存的方式是經過mmap,sbrk系統調用而不是malloc free,若是想profile這些內存,能夠開啓這個變量,默認是false。咱們工程代碼中就有些調用了mmap申請大塊內存的地方,開啓這個環境變量,能更準確跟蹤內存走向。
HEAP_PROFILE_MMAP_ONLY:如其名,只profile mmap,sbrk申請的內存。
更改這些環境變量是能夠在啓動命令中完成的:
% env HEAPPROFILE=/tmp/mybin.hprof /usr/local/bin/my_binary_compiled_with_tcmalloc
算法
CPU profiler的使用方式相似heap-profiler,區別就是要在構建你的程序時不只要連接-ltcmalloc還要連接-lprofiler。它也是須要一個函數調用(ProfilerStart)來開啓,和一個函數調用(ProfilerStop)來關閉,調用這些函數須要include <google/profiler.h>。固然咱們仍是經過內部的自定義指令機制來運行時控制profiler的開啓和關閉。ProfilerStart接受輸出文件名做參數,ProfilerStop關閉profiler時同時輸出含有profile信息的文件,這些信息也是要pprof解析後能夠生成各類可讀格式:
% pprof /bin/ls ls.prof
Enters "interactive" mode
% pprof --text /bin/ls ls.prof
Outputs one line per procedure
% pprof --gv /bin/ls ls.prof
Displays annotated call-graph via 'gv'
% pprof --gv --focus=Mutex /bin/ls ls.prof
Restricts to code paths including a .*Mutex.* entry
% pprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
Code paths including Mutex but not string
% pprof --list=getdir /bin/ls ls.prof
(Per-line) annotated source listing for getdir()
% pprof --disasm=getdir /bin/ls ls.prof
(Per-PC) annotated disassembly for getdir()
% pprof --text localhost:1234
Outputs one line per procedure for localhost:1234
% pprof --callgrind /bin/ls ls.prof
Outputs the call information in callgrind format
咱們實踐中用的最多的是導出成pdf格式,很是直觀,描述文檔在此http://google-perftools.googlecode.com/svn/trunk/doc/heapprofile.html,由於這個有向圖表明的意義相似以前在heap-profiler中所描述的圖,因此再也不多着筆墨。實踐證明CPU profiler效果很好,爲咱們一次次定位了性能熱點,爲此讓人好奇其實現原理。CPU profiler的原理相似許多其餘proflier,都是對運行着的程序定時採樣,最後根據每次記錄的堆棧頻度導出採樣信息。網上有人這樣解釋到:至關於用gdb attach一個正在運行的進程,而後每隔一段時間中斷程序打印堆棧,固然耗時最多的調用最頻繁的堆棧是最常被打印出來的。有稍微看過gperftools這方面的實現,大體就是註冊一個定時觸發的信號(SIGPROF)處理函數,在此函數中獲取當前堆棧信息,經過hash算法以此作hash表的key,放入樣本統計hash table中,若是hash table中已經有一樣的堆棧key了,value就加1,這樣當採樣結束,就把hash table的統計信息導出到文件供pprof程序解析,從而獲得真正直觀的profile信息。 sass