淺談軟件開發(fā)的性能提升
背景
在運行操作軟件的,一個操作執(zhí)行太慢,需要首先分類是IO操作密集引起的問題還是CPU相關的計算密集型問題,軟件的性能優(yōu)化不管是從編碼規(guī)范還是工程項目實踐上來說,都有很多需要我們作為開發(fā)人員注意的方向點。
性能優(yōu)化的目的是為了讓程序執(zhí)行功能變得高效,但同時也不能喪失程序的可維護性和可擴展性。
性能優(yōu)化是一種實驗科學,往往是通過不斷迭代進行,在每次優(yōu)化方案實施完畢后需要對程序的優(yōu)化前后的性能進行對比來驗證優(yōu)化方案的可行性。
下面主要從C和C++語言入手進行一些代碼性能優(yōu)化上去分析,助力開發(fā)相對高性能的軟件。
理論基礎
影響一個軟件程序性能架構的因素主要有兩方面分別為:硬件和軟件。
影響硬性性能方面的因素有:
- 處理計算機體系結構下存儲系統(tǒng)層次結構的排列順序:
- cpu處理器中允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應電路單元處理的技術。
- cpu處理器中的將指令分解為多步,并讓不同指令的各步驟重疊,從而幾條指令并行處理,以加速程序運行過程的,縮短程序執(zhí)行時間。
- cpu中允許同時取得多個任務,并同時去執(zhí)行所取得的的這些任務,并行的效率從代碼層次上強依賴于多進程或多線程代碼,從硬件角度上更多依賴于多核的cpu,把每一個任務分配給每一個處理器獨立完成,在同一時間點,任務一定是同時運行,并行是讓不同代碼片段同時在不同的物理處理器上執(zhí)行。
- 并發(fā):
把任務在不同時間點交給處理器進行處理。
在同一時間點,任務并不會同時運行。
- 其他方面:
內(nèi)存大小、硬盤大小、網(wǎng)絡中的網(wǎng)卡、網(wǎng)速。
影響軟件性能方面的主要因素有:
- 系統(tǒng)函數(shù)調(diào)用開銷
- 編譯器優(yōu)化
- 語言抽象性
軟件的系統(tǒng)函數(shù)調(diào)用:例如 open、read、fread、write、close、mmap、sbrk、time、gettimeofday等系統(tǒng)函數(shù)(因為需要通過系統(tǒng)調(diào)用來和內(nèi)核進行交互)。
編譯器優(yōu)化:在沒有同步原語(包括:互斥鎖操作、內(nèi)存屏障、原子操作等等)的情況下,為了程序的性能編譯器一般可以在當前線程的結果不變的情況下,自由調(diào)整執(zhí)行順序。
語言抽象性(表現(xiàn)為詞匯級和詞法級抽象) : C、C++語言的中間文件是obj文件,它通過在棧上分配了sizeof(obj)字節(jié)空間,它們的時間復雜度都是為0(1),相對于C語言C++面向?qū)ο笾械念悪C制,涉及到類初始化時候的構造函數(shù)調(diào)用,類結束時的析構函數(shù),這會給程序帶來一定性能影響。
編譯器的優(yōu)化
軟件的開發(fā)離不開編譯器工具作為基礎,編譯工具的合理利用也可以為程序性能提升提供助推作用。
下面從編譯器淺談下優(yōu)化的一點點思路。
1.在沒有同步原語(互斥鎖操作、內(nèi)存屏障、原子操作)的情況下,編譯器為了性能可以在當前線程結果不變的情況下自由調(diào)整執(zhí)行順序。
2.在編譯器中,會自動將語句進行等價轉(zhuǎn)換例如:x=a; y=2; 可以自動轉(zhuǎn)換為 y=2; x=a;再入x=y+1; y=x+2 可等價轉(zhuǎn)換為t=y; y+=3;x=t+1。
3.在編譯器中,局部變量可能會被完全消除。
4.全局變量只保證在下一個同步點到來之前寫回到內(nèi)存里。
5.Volatie聲明會禁止編譯器進行相關的優(yōu)化。
6.在編譯器中,可以使用__attribute__((noinline))防止意外內(nèi)聯(lián)。
循環(huán)中的優(yōu)化
程序使用循環(huán)語句,在一定情況下會大大增加計算機中CPU的運算時間和效率。因此在程序中的性能優(yōu)化,循環(huán)語句是一個非常大的技術點需要重點設計考慮。
下面針對循環(huán)語句羅列幾個優(yōu)化的思路方案。
- 把不必要的反復執(zhí)行的代碼提取到循環(huán)外面執(zhí)行。
- 對于頻繁調(diào)用的函數(shù)考慮使用宏定義替換函數(shù),C++引入inline進行優(yōu)化,但是有時函數(shù)體較長時inline不起作用,所以可以考慮對頻繁調(diào)用的函數(shù)改寫為宏定義方式。
- 對一個循環(huán)中多個無相關性的處理拆可以將其分成多個循環(huán)語句,這樣更好的提高cache命中率,在特定場景下可以顯著提升性能。
- 減少循環(huán)體內(nèi)的跳轉(zhuǎn),盡量讓流程順序化執(zhí)行,從循環(huán)中移除不變性代碼。
對象參數(shù)的優(yōu)化
如果不修改對象的情況下,建議使用const obj&方式。
如果需要修改對象的情況下,建議使用obj&方式。
如果需要再對象的新拷貝上進行操作的情況下,建議直接使用obj方式。
String接口的優(yōu)化
- 不推薦使用const String&(除非調(diào)用方確保有現(xiàn)成的String對象);
- 如果不需要修改字符串內(nèi)容,可以使用string_view或const char*;
- 如果只在函數(shù)內(nèi)部修改字符串的內(nèi)容,可以直接使用String方式;
- 如果需要修改調(diào)用者字符串的內(nèi)容,建議使用string&方式。
函數(shù)和虛函數(shù)的優(yōu)化
函數(shù)的調(diào)用使得處理器跳到另外一個代碼地址并回來,這個過程一般需要4個時鐘周期,大多數(shù)情況處理器會把函數(shù)調(diào)用、返回和其他指令一起執(zhí)行以節(jié)約運行時間。函數(shù)的參數(shù)存儲在棧上需要額外的時間( 包括棧幀的建立、saving and restoring registers、可能還有異常信息等)。
下面就針對函數(shù)相關的羅列一些提高性能的思路。
1.避免過多使用不必要的函數(shù),特別在最底層的循環(huán),應該盡量讓代碼在一個函數(shù)內(nèi)??雌饋砼c良好的編碼習慣沖突(一個函數(shù)最好不要超過80行),我們應該知道何時去關注函數(shù)的這些優(yōu)化,而不是一上來就讓代碼可讀性和可為維護性變低。
2.可以使用一些inline函數(shù),讓函數(shù)調(diào)用的地方直接用函數(shù)體替換。Inline它對編譯器來說是個建議,而且不是inline了性能就好,一般當函數(shù)比較小或者只有一個地方調(diào)用的時候,inline效果會相對比較好。
3.減少函數(shù)的間接調(diào)用,如偏向靜態(tài)鏈接而不是動態(tài)鏈接,盡量少用或者不用多繼承、虛擬繼承等風格。
4.優(yōu)先使用迭代而不是遞歸。
5.使用函數(shù)來替換define,從而避免多次求值。宏的其他缺點:不能overload和限制作用域。
6.減少虛函數(shù)的使用,盡可能使用模板方式進行代替虛函數(shù)的使用。
7.類的使用,同時在構造函數(shù)、析構函數(shù)盡可能簡單化使用,消除不必要的反復使用構造函數(shù)和析構函數(shù)。
8.類對象使用時候,復制對象的開銷是高昂的。最好選擇傳遞引用,而不是傳遞值。
運算表達式優(yōu)化
- 在運行過程中,盡量把常量合并到一起。
例如a*x*b==(a*b)*x
- 當在硬件浮點運算單元的機器上double類型會比float效率高,但一般情況下單精度和雙精度的計算性能是一樣的。
- 在除法、取余運算情況下,unsigned ints(無符號類型)會快于 signed ints(有符合類型)。
- 除法中,除以常量會比除以變量效率高,因為可以在編譯期做優(yōu)化,尤其是常量可以表示成2^n時。
- ++i和i++本身性能一樣,但不同的語境情況下,它們的效果是不一樣,如array[i++]比arry[++i]性能好;當依賴自增結果時,++i性能更好,如a=++b,a和b可復用同一個寄存器。
- 浮點除法比乘法慢很多,所以可以利用乘法來代替除法運算,這樣可以提高代碼性能。
內(nèi)存優(yōu)化
程序在運行時,占用內(nèi)存越少,那么它的運行效率也就更快,也說明程序的運行性能較好。那么如果對這塊內(nèi)存進行做優(yōu)化,讓程序達到更好的性能?
下面分析幾種對內(nèi)存優(yōu)化的方案。
- 程序盡量減少對內(nèi)存管理器的調(diào)用次數(shù)。
- 減少內(nèi)存讀寫的操作,特別是減少內(nèi)存寫的次數(shù),并且盡可能按順序進行內(nèi)存的訪問讀取操作。
- 一起使用的函數(shù)存儲在一起。函數(shù)的存儲通常按照源碼中的順序來的,如果函數(shù)A,B,C是一起調(diào)用的,那盡量讓ABC的聲明也按照這個順序。
- 一起使用的變量存儲在一起。使用結構體、對象來定義變量,并通過局部變量方式來聲明,這都是一些較好的選擇
- 動態(tài)內(nèi)存分配、STL容器、string都是一些常容易cache不友好的場景,核心代碼處盡量不進行使用。
算法優(yōu)化
在程序開發(fā)過程中,可以根據(jù)數(shù)據(jù)集的特征選擇更高的數(shù)據(jù)結構和算法策略,這就要求到開發(fā)人員對數(shù)據(jù)結構和算法空間復雜度和時間復雜度有清晰的認識。
在程序中算法會大大影響程序的性能,因此選擇一個合適高效率的算法很重要。
多線程的優(yōu)化
- 多線程加鎖和競爭是影響程序性能的殺手;
- 再多線程中,如果能使用atomic就不要使用mutex;
- 如果讀比寫多很多,使用讀寫鎖(shared_mutex),而不是使用獨占鎖(mutex);
- 使用線程本地(thread_local)變量。
總結
程序的性能優(yōu)化,不僅可以從編譯器、語言特性、編碼習慣、算法選擇、程序架構設計等等方面入手,不斷的提升程序的性能,以此達到用戶體驗感的提升。
最后從項目上梳理幾個可以優(yōu)化的思路點。
1.去除項目中冗余代碼。
2.字符串操作優(yōu)化。
3.減少內(nèi)存分配、釋放操作,例如可以使用內(nèi)存池。
4.減少不必要的互斥鎖操作。
5.根據(jù)性能需求選擇數(shù)據(jù)結構。
6.延遲工作,按需執(zhí)行。
7.減少跨進程的調(diào)用。
8.使用高性能的函數(shù)庫。
9.可以通過使用智能指針代替指針的使用。
10.優(yōu)化動態(tài)庫文件的加載,盡量避免不必要的IO操作。
最后推薦一個很不錯的在線編碼平臺(可以將代碼自動轉(zhuǎn)換為匯編代碼,并且支持多平臺匯編代碼的轉(zhuǎn)換):COMPILER EXPLORER;
后面是平臺鏈接https://godbolt.org/
本文轉(zhuǎn)載自微信公眾號「小道安全」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系小道安全公眾號。