C++程序常見的性能調優方式


轉載自:http://www.708luo.com/?p=36

冗余的變量拷貝

相對C而言,寫C++代碼經常一不小心就會引入一些臨時變量,比如函數實參、函數返回值。在臨時變量之外,也會有其他一些情況會帶來一些冗余的變量拷貝。

之前針對冗余的變量拷貝問題寫過一些帖子,詳情請點擊這里

 

多重過濾

很多服務都會過濾的部分結果的需求,比如游戲交談中過濾需要過濾掉敏感詞。假設現在有兩個過濾詞典,一個詞典A內容較少,另一個詞典B內容較多,現在有1000個詞需要驗證合法性。

詞落在詞典A中的概率是1%,落在詞典B中的概率是10%,而判斷詞是否落在詞典A或B中的操作耗時差不多,記作N。

那么要判斷詞是否合法,有兩種方式:

1. 先判斷詞是否在A中,如果在返回非法;如果不在再判斷是否在B中,如果在返回非法,否則返回合法。

2. 和方式一類似,不過是先判斷是否在B中。

現在我們來計算兩種方式的耗時:

1. 1000*N+1000*(1-1%)*N

2. 1000*N+1000*(1-10%)*N

很明顯,方式二的過濾操作排序優化方式一。

說得有些啰嗦,其實簡單點說就是一句話:多重過濾中把強過濾前移;過濾強度差不多時,過濾消耗較小的前移

如果有些過濾條件較強,但是過濾消耗也較大怎么辦?該前移還是后移?個人到沒遇到過這種情況,如果確實需要考慮,也可以用之前計算方式一、二整體耗時的方法也計算一遍。

 

字符數組的初始化

一些情況是:寫代碼時,很多人為了省事或者說安全起見,每次申請一段內存之后都先全部初始化為0。

另一些情況是:用了一些API,不了解底層實現,把申請的內存全部初始化為0了,比如char buf[1024]=""的方式,有篇帖子寫得比較細,請看這里

上面提到兩種內存初始化為0的情況,其實有些時候並不是必須的。比如把char型數組作為string使用的時候只需要初始化第一個元素為0即可,或者把char型數組作為一個buffer使用的大部分時候根本不需要初始化。

 

頻繁的內存申請、釋放操作

曾經遇到過一個性能問題是:一個服務在啟動了4-5小時之后,性能突然下降。

查看系統狀態發現,這時候CPU的sys態比較高,同時又發現系統的minflt值迅速增加,於是懷疑是內存的申請、釋放造成的性能下降。

最后定位到是服務的處理線程中,在處理請求時有大量申請和釋放內存的操作。定位到原因之后就好辦了,直接把臨時申請的內存改為線程變量,性能一下子回升了。

能夠迅速的懷疑到是臨時的內存申請造成的性能下降,還虧之前看過這篇帖子

至於為什么是4-5小時之后,性能突然下降,則懷疑是內存碎片的問題。

 

提前計算

這里需要提到的有兩類問題:

1. 局部的冗余計算:循環體內的計算提到循環體之前

2. 全局的冗余計算

問題1很簡單,大部分人應該都接觸到過。有人會問編譯器不是對此有對應的優化措施么?對,公共子表達式優化是可以解決一些這個問題。不過實測發現如果循環體內是調用的某個函數,即使這個函數是沒有side effect的,編譯器也無法針對這種情況進行優化。(我是用gcc 3.4.5測試的,不排除更高版本的gcc或者其他編譯器可以針對這種情況進行優化)

對於問題2,我遇到的情況是:服務代碼中定義了一個const變量,假設叫做MAX_X,處理請求是,會計算一個pow(MAX_X)用作過濾閾值,而性能分析發現,這個pow操作占了整體系統CPU占用的10%左右。對於這個問題,我的優化方式很簡單,直接計算定義一個MAX_X_POW變量用作過濾即可。代碼修改2行,性能提升10%。

 

空間換時間

這其實是老生常談、在大學里就經常提到的問題了。

不過第一次深有體會的應用卻是在前段時間剛遇到。簡單來說是這樣一個應用場景:系統內有一份詞表和一份非法詞表,原來的處理邏輯是根據請求中的數據查找到對應的詞(很多),然后用非法詞表過濾掉其中非法的部分。對系統做性能分析發現,依次判斷查找出來的詞是否在非法詞表中的操作比較耗性能,能占整體系統消耗CPU的15-20%。后來的優化手段其實也不復雜,就是服務啟動加載詞表和非法詞表的時候,再生成一張合法詞表,請求再來的時候,直接在合法詞表中查到結果即可。不直接用合法詞表代替原來那份總的詞表的原因是,總的詞表還是其他用途。

 

內聯頻繁調用的短小函數

很多人知道這個問題,但是有時候會不太關注,個人揣測可能的原因有:

1. 編譯器會內聯小函數

2. 覺得函數調用的消耗也不是特別大

針對1,我的看法是,即使編譯器會內聯小函數,如果把函數定義寫在cpp文件中並在另外一個cpp中調用該函數,這時編譯器無法內聯該調用。

針對2,我的實際經驗是,內聯了一個每個請求調用幾百次的get操作之后,響應時間減少5%左右。

 

位運算代替乘除法

據說如果是常量的運算的話,編譯器會自動優化選擇最優的計算方式。這里的常量計算不僅僅是指"4*8"這樣的操作,也可能是"a*b"但編譯的時候編譯器已經可以知道a和b的值。

不過在編譯階段無法知道變量值的時候,將*、/、% 2的冪的運算改為位運算,對性能有時還是蠻有幫助的。

我遇到的一次優化經歷是,將每個請求都會調用幾十到數百次不等的函數中一個*8改為<<3和一個%8改為&7之后,服務器的響應時間減少了5%左右。

下面是我實測的一些數據:

%2的次方可以用位運算代替,a%8=a&7(兩倍多效率提升)

/2的次方可以用移位運算代替,a/8=a>>3(兩倍多效率提升)

*2的次方可以用移位運算代替,a*8=a<<3(小數值測試效率不明顯,大數值1.5倍效率)

整數次方不要用pow,i*i比pow(i,2)快8倍,i*i*i比pow快40倍

strncpy, snprintf效率對比:目標串>>源串 strncpy效率低,源串>>目標串 snprintf效率低

 

編譯優化

gcc編譯的時候,很多服務都是采用O2的優化選項了。不過在使用公共庫的時候,可能沒注意到就使用了一個沒開任何優化的產出了。我就遇到過至少3個服務因為打開了tcmalloc庫的O2選項之后性能提升有10%以上的。

不過開O2優化,有些時候可能會遇到一些非預期的結果,比如這篇帖子提到的memory aliasing的問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM