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