性能,10點系統性思考
作為一個半吊子全棧工匠,在20多年的職業生涯里遇到過太多關于軟件性能的問題。論證或者證明性能的問題往往很關鍵,能否通過一次一個小而有邏輯的可證明可審核的步驟來解決性能問題呢?
曾經企圖創建一種公理化的方法來優化計算機軟件性能,然而能力所限,慚愧之至。退而求其次,希望能夠清楚地系統思考如何優化計算機軟件的性能。
1. 什么是性能?明確概念
性能——performance,有著太多概念外延,在生活中幾乎隨時可見,例如,職場人的performance就是中文里的績效,performance review 就是每人都會面對的績效考核。但是,如果在互聯網上百度一下,大多數有關性能的熱門文章是關于: 計算機軟件執行任何您指定任務所需的時間。
如果把面向對象作為開始,那什么是任務呢?任務,基本上是一個面向業務的工作單元,任務可以嵌套。對于計算機用戶來說,性能通常意味著系統執行某項任務所需的時間。響應時間是任務的執行持續時間,以每個任務的時間為單位,例如,在百度上搜索“性能” 的響應時間為0.2秒左右,在瀏覽器中可以有辦法看到這個測量結果,這就是網頁搜索的一個性能證據。
由于感受軟件性能的主體是人,不同的人對于同樣的軟件能有不同的主觀感受,而且不同的人對于軟件性能關心的視角也不同。有些人眼中的性能是吞吐量,即在指定時間間隔內完成的任務執行數量,例如“每秒點擊次數” 。一般來說,負責團隊性能的人更擔心吞吐量,因為他們要關心該系統是否能夠處理所有用戶需要要處理的所有數據。
那什么是性能呢?時空可能是連續的,從時空的視角看,性能是完成某項任務時所展示出來的時間及時性和空間資源有效性。對用戶而言,更關注及時性,對服務或者產品提供者而言,既關注時間又關注空間,是多種因素的權衡。
2 性能指標——時空糾纏
性能的指標,是指衡量性能的尺度。從時間的維度看,包括響應時間、延遲時間等,從空間的維度看,包括吞吐量,并發用戶數和資源利用率等。
由于時空的內在聯系,以兩個重要的指標為例,吞吐量和響應時間通常相互關聯,但并不完全相同,真正的關系是微妙而復雜的。
通信中的吞吐量與響應時間
假設為某個基準測試以每秒1000個任務的速度度量了吞吐量。那么,用戶的平均響應時間是多少呢?人們很容易認為每個任務的平均響應時間是0.001秒,但事實并非如此。如果處理這個吞吐量的系統是有1000個并行的、獨立的、同質的服務通道,在這種情況下,每個請求可能正好消耗1秒。
現在,可以知道每個任務的平均響應時間在0到1秒之間。然而,不能僅僅從吞吐量測量中推導出響應時間,必須單獨測量它。當然,有數學模型可以計算給定吞吐量的響應時間,但是模型需要更多的輸入,而不僅僅是吞吐量。
計算中的吞吐量與響應時間
在另一個方向上,展露了微妙之處。如果需要在單CPU計算機上編程以提供每秒100個新任務的吞吐量,假設編寫的新任務在計算機系統上執行僅用0.001秒,那么是否能產生所需的吞吐量?如果能在千分之一秒內運行一次任務,那么肯定能在一整秒內至少運行100次。例如,任務請求被很好地序列化,就可以在一個循環中處理所有100個任務,一個接著一個地循序執行。
但是,如果每秒100個任務隨機地出現在系統上,從100個不同的用戶登錄到單 CPU 計算機上,又會怎樣呢?CPU 調度器和序列化資源可能會將吞吐量限制在遠低于每秒100個的任務數量,從而不能完全從響應時間度量推導出吞吐量,需要單獨測量。
響應時間和吞吐量不一定是相反的。要了解這兩者,需要同時測量它們。哪一個更重要呢?對于給定的情況,可以從兩個方向上合理地尋找答案。在許多情況下,答案是兩者都是需要管理的重要指標。例如,系統可能有一個業務需求,不僅要求在99%以上的系統響應中,對給定任務的響應時間必須小于1秒,而且系統必須支持在1秒間隔內持續執行1,000個任務的吞吐量。
3. 描述性能:一切結果,都是概率
“在99%以上的系統響應”,是一種響應時間的期望限定,一些人更習慣于用“平均響應時間必須是 x 秒”來描述。不過,說明目標的百分比方法更好地體現在人們經驗中。
想象一下,對于每天在電腦上執行的某項任務,響應時間容忍度可能是1秒。假設,a系統90% 的平均響應時間是1秒,b系統60% 的平均響應時間是1秒,那么a系統會有10% 的用戶不滿意而b系統有40% 用戶不滿意嗎?如果 a 系統中,90% 的響應時間是0.91秒; 在 b系統 中,90%的響應時間是1.07秒,那么, 這樣的描述比僅僅說1.00秒的平均響應時間更有信息量。
我們嘗試用可能的兩個數來描述世界,一個是均值,一個是方差??蛻舾惺艿降目赡苁欠讲?,而不是均值。將響應時間表示為百分數,可以產生與最終用戶期望相符的性能描述,而且令人信服, 例如,”動態庫加載”的任務必須在至少99.99% 的執行中在小于0.5秒的時間內完成。
我們同樣用概率來描述性能,或許,一切的抽象,可能都歸于數學,一切的結果,可能都歸于概率。
4 問題診斷——以終為始
在曾經遇到的性能問題中,大多數是關于響應時間的: “過去做某事只需要不到一秒的時間,現在有時候需要10多秒。” 當然,一個更樸實的說法是,“整個系統太慢了,簡直不能使用。”
關于性能問題的診斷,最重要的事情是清楚地陳述問題,明確了問題的描述,才能清楚地思考問題。
以終為始,系統想要達到的目標狀態是什么呢?找出一些可以用來表達目標狀態的細節數據: 例如,“在許多情況下,系統的響應時間不超過2秒。如果至少有95% 的關鍵任務響應應時間在一秒以內,這才是我們所要的。”
這樣的描述看起來不錯,但是——
如果用戶沒有這樣一個定量目標呢?
這個特定的目標有兩個量(1s和95%) , 如果不知道其中的某一個該怎么辦呢?
更糟糕的是,如果用戶確實有特定的想法,但是這些期望是不可能實現的,又該怎么辦呢?
如何怎么知道什么是“可能的”或“不可能的” ?......
性能的問題診斷從問題的描述, 以終為始,循序逆推,接下來才是使用工具來應對這些問題。
時間利器——時序圖
時序圖是 UML中指定的一種圖形,用于按照交互發生的順序顯示對象之間的交互。在可視化響應時間方面,時序圖是一個非常有用的工具。
考慮一下繪制時序圖的比例,每個進入的“請求”箭頭和相應的“響應”箭頭之間的距離與服務請求所花費的時間成正比,可以說明圖中表示的組件是如何花費時間的,可以“感覺”到響應時間的相對貢獻。
時序圖可以幫助人們概念化響應時間在給定的系統中是如何被消耗的,還可以很好地顯示同步處理線程是如何并行工作的,除了分析業務,也是性能分析的好工具。但要系統性思考性能,還需要一些其他的東西。假設,要修復任務的響應時間為2048秒,在這段時間內,運行該任務將導致應用程序服務器執行了320,000個數據庫調用。圖3顯示了這個任務的時序圖。
在應用程序和數據庫層之間有太多的請求和響應箭頭,以至于看不到任何細節。也就是說, 在一個很長的滾動條上打印時序圖并不是一個有用的解決方案。
時序圖是一個很好的工具來概念化控制流和相應的時間流,可以作為時間上的利刃,那么有空間利刃么?
空間分析——組件描述直方圖
為了處理那些需要大量調用的任務,需要一個方便的時序集合,這樣就能理解時間如何花費的重要模式。概要描述是響應時間的表格分解,通常按組件響應時間貢獻降序列出。
直方圖一般可以確切地顯示慢速任務在哪里消耗了時間。例如,可以推導出概要描述中標識的每個函數,以及函數調用響應時間所占的百分比,還可以推導出任務期間每種類型的函數調用的平均響應時間。
如果可以深入到聚合為單個調用中持續時間,就可以知道有多少這些調用對應于某個函數的其他調用,并且可以知道每個調用消耗了多少響應時間。“這個任務應該運行多長時間? ” 使用組件描述直方圖,可以構造問題的答案。
老碼農認為,這是問題診斷的第一個重要問題,這是解決性能問題的開端。
5 優化原則——要事優先?
性能改進與程序使用所改進東西的程度成正比。如果正在嘗試改進的事情只占任務總響應時間的5% ,那么能夠產生的最大影響也緊緊是總響應時間的5% 。這意味著,我們越將焦點集中在直方圖的頂部(假設組件直方圖按響應時間降序排列) ,整體響應時間的潛在好處就越大。
但是,這并不意味著總是按照自上而下的順序處理組件的響應,還需要考慮執行補救措施的成本。考慮組件的響應時間直方圖,添加最佳補救方法可以節省多少時間,可以看到每個補救方法的實現成本。
確立優化起點
那么,先采取什么補救措施?成本核算,尋找更好的凈收益,這才是真正需要的優化點。
帶有改進成本的組件響應時間直方圖打開了一扇大門,讓我們可以就首先實施哪些補救措施做出更好的決定,為預測改進后的性能指標提供了一個尺度。進一步,可以找到比預期更有效的方法,以低于預期的成本縮短響應時間。
首先采取什么補救措施取決于對成本估算的信任程度。“非常便宜”是否真的考慮到了所提議的改進可能對系統造成的風險呢?例如,改變這個參數或者刪除那個索引看起來非常經濟,但是這個改變是否有潛在的破壞性?改變了一些現在甚至沒有想到的組件的良好性能呢?可靠的成本估算是技術能力得到體現的另一個領域。
循序漸進中的信譽
另一個值得考慮的因素是可以通過創造小的勝利來獲得的信譽。也許低成本、低風險的改進不會帶來總體響應時間的改進,但是它建立一個小改進的跟蹤記錄,完全符合對于為緩慢的任務節省多少響應時間的預測,也是有價值的。在軟件性能領域,預測和最終實現的跟蹤記錄能夠帶來必要的可信度,以影響我們的同事甚至經理、客戶等等,他們會支持你采取越來越昂貴的補救措施,為企業帶來更大的回報。
需要注意的是,當提出更大而昂貴、高風險的補救方案且獲得支持時,要小心謹慎。信譽是脆弱的,建立很難,但推倒只需要一瞬。
減少相干風險
在實踐中,常常會出現修復一個任務的性能后,結果損害了另一個任務的性能。那么,在性能優化的時候,應該注意些什么呢?
這里,可以類比一個這樣的問題:“為了感覺涼快,是該打開窗子還是脫掉厚衣服呢?”
這就是性能優化的最小化風險原則,確保自己本地的東西是有秩序的,盡量縮小故障域的范圍。如果除了使用一兩個程序之外,所有程序都處理得很好,那么最安全的解決方案就是將范圍本地化在這一兩個程序的修改上。
6 性能中的時空因素
在具體的性能優化過程中,會遇到各種各樣的情況,常見要素包括數據傾斜、執行效率、負載和延遲。
數據傾斜
當處理處理組件響應時間直方圖的時候,可能反復遇到這樣的問題: x個數據庫調用占用了y秒的響應時間。如果能消除一半的調用,能消除多少不必要的響應時間呢?答案往往出人意料,幾乎從來不是“一半的響應時間”, 取決于我們可以消除的單個調用的響應時間。不能假設每個調用的持續時間是平均y/x秒,語句沒有告訴我們調用持續時間是一致的。
數據傾斜是具體調用中的不一致性,出現傾斜的可能性使得無法對組件響應時間提供準確的答案。在不了解任何有關數據傾斜信息的條件下,可以提供的答案是,“在0到y秒之間的某個位置。但是,假設有具體的附加信息。就可以制定出更精確的最佳情況和最差情況估計。在數據庫應用中,讀寫分離也只是大粒度分隔數據傾斜的一種方式。
運行效率
即使整個系統中的每個人都很痛苦,仍然應該首先關注業務需要修復的程序。起點是確保程序盡可能高效地工作。在不增加容量和不犧牲業務功能的情況下, 效率與可消除多少任務執行的總服務時間成反比。換句話說,效率與浪費成反比。
以下是數據庫應用程序中經常出現的2個有關浪費例子:
中間層程序為每一行數據庫插入創建了一個獨立的 SQL 語句。它執行了1000個數據庫prepare調用也就是1000個網絡IO調用 ,而本可以通過一個調用從而減少999個網絡IO調用來完成這項工作。
一條 SQL 語句涉及了數據庫緩沖上萬次,以返回一個幾百行的結果集。而一個額外的過濾語句可以返回終端用戶真正想要看到的6行,只對數據庫緩沖區訪問進行幾十次次觸摸。
當然,如果一個系統存在某些全局性問題,例如,考慮不周的索引、設置糟糕的參數、配置糟糕的硬件等等,會導致整個系統的大量任務效率低下,那么應該修復它。但是,不要為了適應效率低下的程序而調整系統,不要用權宜之計作為永久的解決方案。
解決效率低下的問題往往在解決程序本身效率低下的問題上。即使某些程序是商業化的現成應用程序,從長遠來看,要與軟件供應商合作使程序更有效,而不是試圖優化系統,使其盡可能高效地處理固有的低效率程序。
使程序更高效可以為系統中的每個人帶來巨大的好處,很容易看出減少浪費是如何幫助修復任務的響應時間的。
工作負載
許多人也不明白的是,讓一個程序變得更有效率,會給系統中其他程序帶來性能改進,而這些程序與正在修復的程序沒有明顯的關系。這是由于負載對系統的影響。
負載是由并發任務執行引起的資源競爭。這就是為什么我們的性能測試不能捕捉到生產后期出現的所有性能問題的原因。
負載的一個度量是利用率,即資源使用除以指定時間間隔內的資源容量。隨著資源利用率的提高,用戶從該資源請求服務時的響應時間也會增加。任何一個在高峰時間在北京開過車的人都經歷過這種現象,當交通非常擁擠時,必須在紅綠燈等候更長的時間。
軟件慢下來和汽車是不一樣的,汽車在繁忙的交通中時速30英里而在開闊的道路上時速60英里。由于CPU的每個時鐘周期有固定的指令數量,計算機軟件總是以同樣的速度運行,但是響應時間肯定會隨著系統資源的使用增加而減少。
還是時空的糾纏,隨著負載的增加,系統變慢的原因有兩個: 排隊延遲和一致性延遲。
排隊延遲
負載和響應時間之間的數學關系是眾所周知的。一個稱為 M/M/m 的排隊模型將響應時間與滿足一組特定需求的系統負載聯系了起來。M/M/m 有一個假設,即系統具有“理論上完美的可伸縮性” ,盡管有一些過分,但 M/M/m 模型在性能方面還是有很多值得我們學習的地方。下圖顯示了m=8時該模型的響應時間和負載之間的關系。
【本文來自51CTO專欄作者“老曹”的原創文章,作者微信公眾號:喔家ArchiSelf,id:wrieless-com】