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