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