聊聊性能調(diào)優(yōu)什么時候應(yīng)該停止?
在我以往參與性能優(yōu)化項目的經(jīng)歷中,不止一次有人問到這樣一個問題:軟件性能調(diào)優(yōu)究竟什么時候應(yīng)該停止呢?我發(fā)現(xiàn)很多研發(fā)人員在進行性能調(diào)優(yōu)的過程中,進展往往并不理想。由于性能優(yōu)化目標(biāo)遲遲未能達(dá)成,他們陷入了對性能調(diào)優(yōu)何時才能結(jié)束的迷茫之中。
其實,這個問題也曾困擾過我。記得在參與第一個性能優(yōu)化項目時,我每天的工作就是尋找代碼中的低效率實現(xiàn),然后進行修改重構(gòu),并驗證性能提升效果,如此日復(fù)一日。所以,當(dāng)時我很想說服團隊 Leader 結(jié)束這個性能調(diào)優(yōu)任務(wù),但我首先連自己都說服不了。也正是基于這個原因,我才開始認(rèn)真思考這個問題。
為什么會提出這個問題?
我們先來做一個假設(shè),假如現(xiàn)在團隊開發(fā)的一個軟件產(chǎn)品需要進行性能調(diào)優(yōu),其指定的性能調(diào)優(yōu)目標(biāo)為提升 20%。那么,我們來思考一下,這個目標(biāo)好達(dá)成嗎?實際上,在進行深入的性能分析之前,我們很難回答這個問題。原因在于,不同軟件的設(shè)計與實現(xiàn)存在很大差異,而針對性能這個模塊,我們可以優(yōu)化提升的空間各不相同。
我舉個真實的例子,在我曾經(jīng)參與的一個協(xié)議棧報文子系統(tǒng)的性能優(yōu)化項目中,僅僅因為在代碼實現(xiàn)優(yōu)化中減少了一次內(nèi)存拷貝,就一次性將系統(tǒng)處理的性能提升了 20%。然而,在我參與的另一個配置管理子系統(tǒng)的優(yōu)化項目中,由于沒有找到比較大的性能優(yōu)化點,所以花費了很長時間與精力,才將性能提升了 10% 左右。
所以我才說,不同軟件系統(tǒng)的性能優(yōu)化提升效果和優(yōu)化投入成本之間的關(guān)系差異很大,具體可以參考下圖:
圖片
在這個圖中,有兩個比較明顯的規(guī)律值得你觀察。其一,不同軟件系統(tǒng)(軟件 A 和軟件 B)在性能調(diào)優(yōu)的過程中,能夠達(dá)到的性能提升百分比上限是不同的。其二,在性能調(diào)優(yōu)的前期,投入很少成本就能獲取比較好的性能提升效果;但在性能調(diào)優(yōu)的中后期,要獲取同樣多的性能收益,需要花費的精力和成本會越來越大。
其實,在進行性能調(diào)優(yōu)時,首要追求的目標(biāo)應(yīng)該是最大的投資收益比,也就是獲取的性能優(yōu)化收益值和消耗工作量成本之間的比值要最高。所以在理想情況下,我們應(yīng)該將性能調(diào)優(yōu)目標(biāo)設(shè)定到一個性能提升臨界值(通常會接近性能提升的上限)。如果達(dá)到這個臨界值,就意味著即使后續(xù)進行再多的性能調(diào)優(yōu)工作,我們能獲取的性能收益都會越來越有限。那么在這個時候,我們就可以適當(dāng)調(diào)整性能調(diào)優(yōu)的節(jié)奏。如前面的示意圖所示,軟件 A 和軟件 B 的性能調(diào)優(yōu)目標(biāo)設(shè)置的臨界值,可能會在三角形所標(biāo)識的位置附近。
但問題在于,對于一個軟件系統(tǒng)來說,性能調(diào)優(yōu)提升目標(biāo)的臨界值設(shè)定為多少才是合理的呢?我們又該如何確定這個臨界值呢?
一般情況下,研發(fā)團隊在設(shè)定性能調(diào)優(yōu)目標(biāo)時,會采取兩種方式。第一種是以客戶關(guān)注的性能需求目標(biāo)為導(dǎo)向。比如我之前參與的百萬表單數(shù)據(jù)查詢分析優(yōu)化項目,其核心目標(biāo)就是讓客戶在操作過程中不卡頓,所以只需把查詢請求響應(yīng)時間優(yōu)化到 1 秒內(nèi)即可。第二種是以降低產(chǎn)品的部署運維成本為導(dǎo)向。這種方式通常會先確定一個性能提升百分比,比如將系統(tǒng)服務(wù)的響應(yīng)時間降低 20%(從 100ms 到 80ms),減少產(chǎn)品部署使用的集群機器規(guī)模 20% 等等。
不過這里要注意,不管采用哪種方式制定的性能調(diào)優(yōu)目標(biāo),都可能無法與軟件優(yōu)化可以達(dá)到的臨界值完全匹配。在這種場景下,很容易導(dǎo)致性能調(diào)優(yōu)的目標(biāo)沒有達(dá)成,但是性能調(diào)優(yōu)任務(wù)卻無法繼續(xù)開展的情況。
所以,我們在性能調(diào)優(yōu)的過程中,一定要謹(jǐn)記一點:未經(jīng)分析就敲定性能優(yōu)化的目標(biāo)是不可取的。既然如此,那么正確開展和實施性能調(diào)優(yōu)的方法步驟是什么呢?下面我就帶你來分析分析。
正確開展性能調(diào)優(yōu)的方法步驟
實際上,在很多研發(fā)團隊的心目中,性能調(diào)優(yōu)工作可能就是選擇一款代碼 Profiling 工具,然后針對軟件執(zhí)行期間進行性能分析,逐個尋找熱點函數(shù),最后進行修改和優(yōu)化。然而,我們要知道,這種方法存在很大的局限性,它能夠識別出的性能優(yōu)化點非常有限。
比如說,并發(fā)設(shè)計、通信設(shè)計、IO 設(shè)計等軟件設(shè)計引入的性能問題,它無法識別出來;不僅如此,軟件編碼實現(xiàn)層引入的性能問題,比如數(shù)據(jù)結(jié)構(gòu)和算法選擇等,它也都無法識別出來。
所以在這里,我根據(jù)以往參與的性能優(yōu)化項目經(jīng)驗,總結(jié)出了實施性能調(diào)優(yōu)的方法步驟。接下來,我就給你具體分析一下。
第一步,進行系統(tǒng)性的性能優(yōu)化分析診斷。在此過程中,自頂向下地分析并識別所有可能導(dǎo)致性能劣化的可優(yōu)化點。從這里輸出的內(nèi)容應(yīng)當(dāng)包含軟件設(shè)計優(yōu)化點、軟件實現(xiàn)優(yōu)化點等較為完整的列表,例如調(diào)整并發(fā)任務(wù)拆分、調(diào)整數(shù)據(jù)結(jié)構(gòu)、選擇性能優(yōu)化模式等等。
第二步,分析調(diào)整性能調(diào)優(yōu)目標(biāo)值。這一步是指根據(jù)識別出的性能優(yōu)化點,分析修改后的性能提升收益。需要注意的是,針對每個優(yōu)化點的分析過程各不相同,且并沒有統(tǒng)一的方法可供參考。
為了幫助你更好地理解這個過程,我舉兩個以前參與的性能優(yōu)化案例來具體說明。
案例 1:一個協(xié)議棧報文子系統(tǒng)的性能優(yōu)化項目。在這個項目中,我們通過性能優(yōu)化分析診斷后發(fā)現(xiàn),業(yè)務(wù)在處理過程中對報文數(shù)據(jù)執(zhí)行了一次 copy 操作,而協(xié)議在處理過程中只修改了報文數(shù)據(jù)頭部很少一部分字節(jié)的信息。在這種場景下,業(yè)務(wù)中的 copy 操作開銷可以優(yōu)化掉。那么優(yōu)化修改后的性能提升值有多少呢?這里我根據(jù) copy 的數(shù)據(jù)量在單板上進行了測量計算,在優(yōu)化修改之前計算出了性能的預(yù)期收益。
案例 2:一個后端微服務(wù)的性能優(yōu)化項目。在這個項目中,經(jīng)過性能優(yōu)化分析診斷后發(fā)現(xiàn),業(yè)務(wù)存在很多慢查詢操作,對軟件性能影響較大。進一步分析后發(fā)現(xiàn),這些慢查詢所獲取的數(shù)據(jù)其實很少變化,所以考慮采用緩存策略來優(yōu)化性能。在這種場景下,可以根據(jù)慢查詢的請求處理時延和請求的頻次,分析計算出引入 Cache 場景下的性能提升收益。
總之,對于性能優(yōu)化點來說,性能提升收益分析是一個非常重要的環(huán)節(jié),不應(yīng)被忽視。
第三步,按照成本收益逐步實施性能調(diào)優(yōu)。
接下來,我們可以對性能優(yōu)化點按照優(yōu)先級進行排序,然后逐步修改并驗證優(yōu)化效果。在對性能優(yōu)化點進行排序時,我們需要考慮的主要因素有幾個:性能收益的大小、修改的工作量大小,以及對軟件質(zhì)量產(chǎn)生的影響(比如導(dǎo)致軟件變復(fù)雜、引入故障風(fēng)險高等)。
另外,這里要記住,如果對編譯期選項配置優(yōu)化和編碼實現(xiàn)優(yōu)化進行優(yōu)先級排序,在同等性能收益的情況下,一般來說編譯期優(yōu)化的修改工作量會比較小,引入故障的風(fēng)險率也比較低,所以優(yōu)先級應(yīng)該更高一些。
第四步,增加完善性能基線測試。
當(dāng)性能調(diào)優(yōu)完成合入后,就可以同步修改完善性能基線測試。然而,事實上很少有研發(fā)團隊能夠按照上述步驟來實施性能調(diào)優(yōu),因此在性能調(diào)優(yōu)過程中容易陷入僵局,花費很大精力卻并未給軟件產(chǎn)品帶來價值提升。在這個時候,研發(fā)團隊就應(yīng)該及時喊停,重新調(diào)整性能調(diào)優(yōu)的工作方式與節(jié)奏。
什么時候需要喊停性能調(diào)優(yōu)工作?
第一種性能調(diào)優(yōu)反模式是:性能調(diào)優(yōu)嚴(yán)重破壞了軟件的質(zhì)量。
這里舉一個真實的案例。在我曾經(jīng)參與的一個嵌入式系統(tǒng)性能優(yōu)化項目中,原來的性能優(yōu)化團隊發(fā)現(xiàn),通過宏替換個別函數(shù)調(diào)用會帶來性能提升,于是幾乎將代碼中的所有函數(shù)都通過宏重新實現(xiàn)來整改替換。最后導(dǎo)致的后果是:大量的宏實現(xiàn)函數(shù)導(dǎo)致代碼編寫和閱讀成本顯著增大;同時在代碼整改的過程中,引入了非常多的故障,而且很長時間無法得到很好的解決;更糟糕的是,最后的軟件性能優(yōu)化效果也沒有達(dá)到預(yù)期。
其實,這種嚴(yán)重破壞軟件設(shè)計質(zhì)量的性能調(diào)優(yōu)還是比較普遍的。比如,在代碼中隨意添加條件分支進行特殊處理,最后因為加入太多特殊流程,導(dǎo)致代碼很難再添加新的業(yè)務(wù)特性。
第二種性能調(diào)優(yōu)反模式是:盲目修改代碼來嘗試優(yōu)化。
有的性能優(yōu)化團隊為了提升指令 Cache 命中率,會隨機調(diào)整函數(shù)的位置。比如,把一個函數(shù)從一個文件中搬移到另外一個文件中;或者把一個函數(shù)從一個類搬移到另外一個類中,來判斷 Cache 命中率是否有提升。這種性能調(diào)優(yōu)方式,由于背后并沒有理論指導(dǎo),即使可以獲取到一些短暫的性能提升收益,也是不穩(wěn)定的,所以我們應(yīng)該盡量避免這樣做。
第三種性能調(diào)優(yōu)反模式是:在業(yè)務(wù)的非性能瓶頸點上反復(fù)調(diào)優(yōu)。
舉個簡單的例子,軟件的查詢請求處理的吞吐量,受制于底層網(wǎng)絡(luò)傳輸帶寬值的上限,理論上不可能再提升。這個時候,還在持續(xù)分析調(diào)優(yōu)軟件實現(xiàn),期望提升吞吐量是沒有任何意義的。
第四種性能調(diào)優(yōu)反模式是:沒有價值驅(qū)動的性能調(diào)優(yōu)。
其實這種情況也挺常見,在軟件系統(tǒng)中存在一些服務(wù) / 組件(比如:操作事務(wù)記錄,配置管理后臺等),它們的處理性能并不會直接影響用戶感受,而且占用的機器資源都很少,這時候如果還投入很大的工作量去優(yōu)化軟件性能,其實是沒有意義的。