程式的cpu問題是另外一類典型的程式效能問題,很多開發人員都受到過程式cpu占用過高的困擾。本次我們收集了14個cpu類的問題,和大家一起分析下這些問題的種類和原因。另外,對於c/c++程式而言,目前已經有了很多cpu問題定位的工具,本文也會進行比較分析。
程式cpu類問題的主要現象是:程式占用的cpu過高,比程式公升級前有很大的公升高。導致程式cpu占用過高的主要原因是程式設計不合理,絕大部分的cpu問題都是程式設計的問題。因此,提高程式的設計質量是避免cpu問題的主要手段。
在程式設計中,有些程式的寫法是比較低效的,沒有經驗的同學很容易使用一些低效的函式或方法,這就是我們常說的「坑」。我們蒐集到了一些「坑」,跟大家分享下。
memset是乙個很常見的效能坑。如果在程式中使用的memset過多,會導致程式的cpu消耗很大。memset使用過多,往往在不經意間就讓程式下降了一大截。關於memset函式,一種常見的誤用是在迴圈中對較大的資料結構進行memset。在這個例子中,乙個query中memset 1m的內容,在整體1500qps的情況下,每秒進行重置的記憶體達到1.5g,導致程式的cpu idle答覆下降。
上面這個例子,是memset一種比較明顯的問題,通過**review等方式是比較容易發現的。在一些情況下,memset操作是在隱式發生的,問題的排查難度也隨之加大。
#**片段1
char buffer[1024] = ;
**片段1中的簡簡單單的一行**,其實在實際的執行過程中是會呼叫memset的。這個就是乙個坑:在棧記憶體中申請緩衝區,然後再賦值,會隱式的呼叫memset,將記憶體初始化為0。這個問題也導致了乙個產品線的核心模組效能大幅下降,引起了嚴重的效能問題。
另外,在使用一些系統函式或庫函式時,也需要仔細閱讀使用手冊,避免出現大量的無效的記憶體申請、釋放和重置操作。
**片段2
memset(&t_data->preq, 0, sizeof(pusrinfo_req_t));
memset(&t_data->pres, 0, sizeof(pusrinfo_res_t)); odb_renew(t_data->cur_field_dict);
**片段2中的這段**中,第1、2行中的memset會導致程式的cpu使用過多,但即使是將這兩行的**注釋掉,程式的效能依然沒有明顯的改觀。問題的根源在於**片段2中最後一行**呼叫的odb_renew函式有釋放記憶體和大量的memset操作,導致消耗的cpu很多。如果在程式中呼叫了大量的odb_renew函式,其效能一定不太好。
strncpy這個字串操作函式是比較耗費效能的,同strncpy函式實現類似功能的函式有snprintf和memcpy+strlen這兩種方式。表1是在一台測試機上對這三種方式的效能比較。
從表1可以看出,memcpy的效能最好。令人欣喜的是snprintf在大資料下效能漸漸逼近memcpy。稍微看了一下幾個函式的源**,memcpy用了page copy和word copy結合,所以效能優化的比較好,而且strlen也是用4位元組做迴圈步長的。strncpy只是簡單地逐字節拷貝,並且會將目標buffer後面所有的空閒空間全部填為0,這在很多情況下是非常耗費效能的。
整體上,對於這類問題的主要解決方法是:識別cpu消耗多的函式並且儘量減少這類函式的使用。比如,有些資料結構的memset是沒有必要的,這些資料結構會被下乙個query的資料自然填充。又或者採用更高效的初始化的方法。典型的例子是,字串陣列的初始化,只需要將第乙個字元設定為0即可。
程式設計中,容器的使用是必不可少的。不同型別的容器,其設計的目的是不同的,因此某些方面的效能天然地會比較低。我們在程式設計的時候,要能夠正確的識別容器各種用法的效能,減少低效的使用。
**片段3中的第6行**,將計算列表長度的方法放到了迴圈中,本身list型別求取長度的函式複雜度就是o(n),在這個操作放到迴圈中以後,直接將這段**的複雜度提高到了o(n2),在列表中元素較多的情況下,對程式的效能將產生非常大的影響。**片段3中的例子2是另外一種錯誤用法,演算法複雜度也是o(n2)。
上面的兩個例子,還有乙個典型的特點,就是存在迴圈。對於迴圈程式來說,要盡量避免在迴圈體內進行大量消耗cpu的操作。即使是每次消耗的cpu較少,但是由於存在迴圈,演算法複雜度提公升了乙個數量級,因此要特別的小心。
對於c/c++程式,目前業界使用的比較多的cpu熱點定位工具有:valgrind中元件callgrind,gprof(gnu profiler),google perf tools元件中的cpu profiler和oprofiler。
• callgrind工具(valgrind套件之一):valgrind整體採用虛擬機器的解決方案,將被測程式的指令轉換了valgrind自身的**ucode,這樣就可以實現對被測程式全面的分析(cpu, mem)。
• gprof(gnu profiler)工具 : gnu提供的工具,已經存在了30年左右了。主要通過在函式入口處插入**的方式來統計函式的呼叫關係、次數及cpu使用方式。
• google perf tools(cpu profile):對程式的呼叫棧進行取樣分析,通過呼叫棧反推出函式的呼叫次數、關係和cpu消耗時間。
表2從多個維度對這4種工具進行了比較,綜合比較這些因素後,我還是推薦使用google perf tools套件中的cpu profiler,這個工具在靈活性、應用性等方面的優勢非常明顯。但就像**中提到的,這種工具會讓程式一定概率core dump。
本文收集並分析了十幾個c/c++程式cpu效能問題,通過對這些問題的分析,我們發現cpu相關的效能問題,很多都是由於程式設計問題引起的。減少低效的呼叫,充分釋放cpu的能力,是提公升程式cpu效能的關鍵。從更大的層面上來看,程式的cpu效能還需要更好的架構設計,充分呼叫各種資源來高效地完成任務。google perf tools套件中的cpu profiler工具是乙個非常優秀的定位cpu熱點的工具,希望大家能夠多用這類工具來優化程式的cpu。
C C 程式占用記憶體分析
首先感謝下原作者,寫的真的非常明白,非常詳細 乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os ...
C C 程式占用記憶體分析
首先感謝下原作者,寫的真的非常明白,可能這是我看過的最詳細的堆和棧記憶體分析了 乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設...
C C 程式崩潰原因分析
我們執行程式時經常會遇到異常崩潰,也就是我們常說的crash,下面我想總結一下crash出現的原因。而導致crash的主要原因就是段錯誤 segmentation fault 是不是很熟悉,相信每個執行過c程式的小夥伴都見過這兩個單詞,而且這種錯誤一般不給其他提示,看著很糾結。導致段錯誤的原因一般有...