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