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























