系統(tǒng)性能設(shè)計(jì)的10個(gè)反模式
從用戶界面到應(yīng)用程序,從驅(qū)動(dòng)程序到操作系統(tǒng)的內(nèi)核,幾乎所有軟件都存在著系統(tǒng)性能上的缺陷,許多看起來(lái)完全不同的性能問(wèn)題實(shí)際上有著相同的根本原因。對(duì)于成功經(jīng)驗(yàn)的抽象一般被稱為軟件模式或者設(shè)計(jì)模式,那么導(dǎo)致系統(tǒng)性能問(wèn)題的行為方式和做法則可以稱為性能設(shè)計(jì)的反模式。
有些反模式的根源在于硬件問(wèn)題,有些是開(kāi)發(fā)或管理實(shí)踐不佳的結(jié)果,還有一些只是常見(jiàn)的錯(cuò)誤。這里列出了10個(gè)影響系統(tǒng)性能的反模式, 它們產(chǎn)生的原因是什么?如何發(fā)現(xiàn)以及如何避免呢?

1. 項(xiàng)目結(jié)束時(shí)來(lái)修復(fù)性能
在軟件項(xiàng)目的開(kāi)發(fā)過(guò)程中,一個(gè)經(jīng)常被忽視的領(lǐng)域就是性能的測(cè)量和評(píng)估。很多時(shí)候,團(tuán)隊(duì)都會(huì)爭(zhēng)分奪秒地開(kāi)發(fā)新特性并修復(fù) bug,而性能工作則會(huì)被拋在腦后。人們常常無(wú)法制定性能目標(biāo)或基準(zhǔn),而且開(kāi)發(fā)人員第一次考慮性能則是在收到性能問(wèn)題的報(bào)告之后。。
這種反模式看起來(lái)很明顯,但是許多項(xiàng)目總是在重蹈覆轍。如果團(tuán)隊(duì)不費(fèi)心去建模或測(cè)量軟件性能,或者等到項(xiàng)目接近尾聲才開(kāi)始,不太可能得到好的結(jié)果,即便成功也是偶然的。
2.測(cè)量和比較的不當(dāng)
選擇一個(gè)基準(zhǔn)和比較結(jié)果在軟件項(xiàng)目開(kāi)始階段是一個(gè)簡(jiǎn)單的問(wèn)題,但在持續(xù)的過(guò)程中會(huì)發(fā)生很多問(wèn)題。例如,在軟件系統(tǒng)基準(zhǔn)測(cè)試上,性能指標(biāo)比以前的版本降低不超過(guò)2% ,這就是一個(gè)典型的錯(cuò)誤,這意味著系統(tǒng)性能會(huì)隨著時(shí)間的推移而下降。
那么,如何選擇度量的基準(zhǔn)呢? 一個(gè)好基準(zhǔn)的特點(diǎn)是:
可重復(fù)性,比較實(shí)驗(yàn)可以相對(duì)容易地進(jìn)行,而且精確度合理。
可觀察性,如果表現(xiàn)不佳,開(kāi)發(fā)者有一個(gè)可以開(kāi)始發(fā)現(xiàn)的地方。
可持續(xù)性,如果和競(jìng)品進(jìn)行比較,維護(hù)以前版本的性能歷史記錄是一個(gè)有價(jià)值的幫助。
易于表達(dá),讓每個(gè)人都能在簡(jiǎn)短的演示中理解比較的結(jié)果。
真實(shí)性,使測(cè)量真實(shí)地反映了客戶的體驗(yàn)。
可執(zhí)行性,所有開(kāi)發(fā)人員能夠迅速確定其更改的效果。
并不是所有被選中的基準(zhǔn)都能滿足所有這些標(biāo)準(zhǔn),但避免選擇不能真正代表用戶體驗(yàn)的基準(zhǔn),并抵制為基準(zhǔn)進(jìn)行優(yōu)化的誘惑。
3. 對(duì)算法的厭惡
對(duì)于許多軟件開(kāi)發(fā)者來(lái)說(shuō),可能有著『算法恐懼癥』。實(shí)際上,很多性能問(wèn)題的根本原因是在于軟件中所采用的算法,真正重大的改進(jìn)都源于算法的改變,例如,性能提高1倍以上。算法選擇的一個(gè)關(guān)鍵部分是手頭有一個(gè)現(xiàn)實(shí)的基準(zhǔn)或測(cè)量實(shí)踐來(lái)支撐,而不是直覺(jué)或者所謂的最佳實(shí)踐。這意味著執(zhí)行性能最有效時(shí)間是在項(xiàng)目的早期階段,這可能與通常發(fā)生的情況正好相反。在處理o (n2)算法時(shí),所有的編譯選項(xiàng)幾乎是沒(méi)啥用的。
為了彌補(bǔ)硬件差異,根據(jù)運(yùn)行環(huán)境做出明顯不同的選擇。捕捉這類問(wèn)題的方法之一是,原始開(kāi)發(fā)人員既要記錄影響代碼性能的外部性的假設(shè),又要提供某種程序化的方法來(lái)驗(yàn)證這些假設(shè)。例如,在判斷哈希算法的時(shí)候,跟蹤哈希表上的最大哈希鏈長(zhǎng)度以及哈希表總數(shù),這樣就可以輕松識(shí)別哈希函數(shù)的優(yōu)劣。另一種方式是當(dāng)假設(shè)被違背時(shí)強(qiáng)制一個(gè)報(bào)錯(cuò),這可能不適合某些應(yīng)用程序。軟件重用是一個(gè)很好的目標(biāo),但是要注意不要違背在其開(kāi)發(fā)過(guò)程中所做的假設(shè)。

4. 遞歸的泛濫
如果你的應(yīng)用程序正在做一些不需要或不值得欣賞的工作,比如多次重新繪制屏幕、過(guò)于頻繁地計(jì)算統(tǒng)計(jì)數(shù)據(jù)等等,那么消除這種浪費(fèi)就是性能提升的重要領(lǐng)域。對(duì)于應(yīng)用程序來(lái)說(shuō),通常最重要的是程序的結(jié)束狀態(tài),而不是到達(dá)結(jié)束狀態(tài)所需的一系列步驟。通常有一條捷徑可以讓我們更快地達(dá)到目標(biāo),這就像縮短賽道而不是加速賽車,除了正確的使用預(yù)測(cè)預(yù)取之外,軟件中加快速度的唯一方法就是做得更少。
5. 過(guò)早地進(jìn)行低級(jí)別的優(yōu)化
過(guò)早的優(yōu)化可能對(duì)實(shí)際基準(zhǔn)測(cè)試的性能產(chǎn)生負(fù)面影響,低層次的周期調(diào)整不是在最初的代碼開(kāi)發(fā)階段。即便如此,也應(yīng)該仔細(xì)記錄進(jìn)行調(diào)優(yōu)的條件,以幫助其他人以后評(píng)估這些條件是否仍然有效。
如前所述,最有效的軟件性能工作側(cè)重于算法,而不是低級(jí)別的細(xì)節(jié),手工編碼的匯編鏈表是愚蠢的事情。另外,這種低級(jí)別的優(yōu)化不是一個(gè)好主意,對(duì)于需要在不同的系統(tǒng)或處理器集上良好運(yùn)行的軟件,這些技術(shù)往往需要每個(gè)平臺(tái)的不同版本,從開(kāi)發(fā)、可移植性和測(cè)試角度來(lái)看,這是一種痛苦而昂貴的方法。基于實(shí)際實(shí)驗(yàn)的結(jié)果,而不是直覺(jué),直到確信這是一種提高性能的經(jīng)濟(jì)有效的方法。Donald Knuth 說(shuō)過(guò): “過(guò)早的優(yōu)化是萬(wàn)惡之源。”
6.關(guān)注表象而不是真正到問(wèn)題
人們經(jīng)常要求確定應(yīng)用程序性能不佳的原因,通常認(rèn)為一定有某種潛在bug導(dǎo)致了性能問(wèn)題的出現(xiàn)。盡管這有時(shí)確實(shí)如此,但在大多數(shù)情況下,問(wèn)題實(shí)際上出在應(yīng)用程序本身,而且往往出在用戶使用應(yīng)用程序的方式上。
應(yīng)用程序頂層的每一行代碼通常都會(huì)導(dǎo)致軟件堆棧深處的大量工作,頂層的低效率會(huì)有一個(gè)很大的杠桿系數(shù),放大了它們的影響。然而,由于缺乏觀察應(yīng)用程序行為的合適工具,工程師一般會(huì)嘗試使用 trace 或諸如 iostat 等各種性能監(jiān)視命令等低級(jí)工具來(lái)診斷性能問(wèn)題,這常常會(huì)導(dǎo)致增加系統(tǒng)調(diào)用或 i/o 操作,而不是修改應(yīng)用程序以減少正在進(jìn)行的調(diào)用數(shù)量。例如,一個(gè)基于數(shù)據(jù)庫(kù)的應(yīng)用程序有性能問(wèn)題,那么首先查看 SQL; 一旦調(diào)優(yōu)了 SQL,數(shù)據(jù)庫(kù)正確地編制了索引等,那么也許是時(shí)候查看磁盤(pán)利用率了。

7.線程數(shù)量過(guò)多
一旦程序員熟悉了線程或者多個(gè)協(xié)程,一個(gè)更常見(jiàn)的錯(cuò)誤是決定對(duì)每個(gè)連接的工作單元使用一個(gè)線程(或進(jìn)程)。無(wú)可否認(rèn),這是一個(gè)簡(jiǎn)單的編程模型,任務(wù)狀態(tài)可以方便地保存在線程堆棧中。這在小型或本地測(cè)試環(huán)境中工作得很好,一旦這個(gè)應(yīng)用程序被部署到成千上萬(wàn)個(gè)緩慢的連接上時(shí),就會(huì)發(fā)現(xiàn)這些成千上萬(wàn)的線程并沒(méi)有真正很好地執(zhí)行,因?yàn)檫@臺(tái)機(jī)器現(xiàn)在有大量的 TLB 和來(lái)自所有這些堆棧的緩存壓力。
一個(gè)經(jīng)驗(yàn)性的答案是將線程數(shù)量限制在一個(gè)更合理的數(shù)量(接近 cpu 數(shù)量) ,并使用工作堆模型和異步 i/o 針對(duì)要完成的任務(wù)多路傳送。這些類型的應(yīng)用程序架構(gòu)更容易擴(kuò)展,并且在重負(fù)載下表現(xiàn)得更優(yōu)雅。畢竟,如果應(yīng)用程序的凈吞吐量開(kāi)始下降,超過(guò)一定的負(fù)載水平,這種情況本身就是不穩(wěn)定的。
8. 非對(duì)稱硬件利用率
一些處理器設(shè)計(jì)使用三級(jí)緩存來(lái)隱藏內(nèi)存訪問(wèn)的延遲,多級(jí)TLB現(xiàn)在也變得越來(lái)越普遍。這些緩存和 TLB使用不同程度的關(guān)聯(lián)方式來(lái)跨緩存分散應(yīng)用程序的負(fù)載,但是這種技術(shù)常常被其他性能優(yōu)化意外地阻礙。一個(gè)簡(jiǎn)單的例子是一個(gè)性能工具,它將一個(gè)共享庫(kù)中的函數(shù)重新排序,將最常用的函數(shù)放在庫(kù)的開(kāi)頭,這是一個(gè)顯然合理的策略,可以減少 ITLB的錯(cuò)誤率和分頁(yè)次數(shù)。然而,當(dāng)應(yīng)用于許多共享庫(kù)時(shí),這將導(dǎo)致每個(gè)共享庫(kù)的段開(kāi)頭被訪問(wèn)的頻率遠(yuǎn)遠(yuǎn)高于其他部分。如果是64kb 的對(duì)齊方式,這意味著那些屬于64kb 邊界的 TLB 條目更為常用,有效地將 ITLB 的大小減少了8倍。另一個(gè)例子發(fā)生在數(shù)據(jù)庫(kù)上,如果該數(shù)據(jù)庫(kù)代碼以 L2緩存大小的倍數(shù)分配了大塊共享內(nèi)存,通過(guò)在每個(gè)大塊中使用類似的訪問(wèn)模式,顯著降低了 L2緩存的命中率。熱點(diǎn)檢測(cè)還可能發(fā)生在物理內(nèi)存上,如果一個(gè)內(nèi)存區(qū)域優(yōu)于另一個(gè)區(qū)域,則可能導(dǎo)致平均內(nèi)存訪問(wèn)時(shí)間顯著增加。
檢測(cè)和避免這一問(wèn)題可能很困難,因?yàn)橛布?jì)數(shù)器和工具往往缺乏直接觀測(cè)這些影響的能力; 一旦檢測(cè)到,如果沒(méi)有應(yīng)用程序可見(jiàn)的影響,則很難避免這種熱點(diǎn)定位,通常需要有意識(shí)地將隨機(jī)性注入分配模式、內(nèi)存布局等。
9. CPU之間無(wú)需交換緩存
在多處理器上,精心設(shè)計(jì)的硬件協(xié)議確保系統(tǒng)中只有一個(gè)緩存包含修改版本的內(nèi)存; 多個(gè)緩存可能包含未修改的內(nèi)存副本。當(dāng)一個(gè) CPU 試圖寫(xiě)入當(dāng)前位于另一個(gè) CPU 緩存中的內(nèi)存時(shí),會(huì)發(fā)生緩存到緩存的傳輸,以便在 CPU 之間移動(dòng)該緩存的所有權(quán)。在大型多處理器上,這可能需要大量的時(shí)間和可用帶寬; 最小化這些傳輸?shù)臄?shù)量可以提高可測(cè)量性。
一個(gè)經(jīng)常看到的例子是一個(gè)簡(jiǎn)單的計(jì)數(shù)器,它受到讀取鎖的保護(hù)。鎖被獲取,計(jì)數(shù)器讀取,鎖被丟棄。這些鎖除了毫無(wú)必要地降低可伸縮性之外,幾乎什么都不做。經(jīng)常影響性能的問(wèn)題是鎖的濫用。如果讀取器鎖的持有時(shí)間很短 ,那么我們最好只使用簡(jiǎn)單的互斥鎖,當(dāng)鎖被長(zhǎng)時(shí)間持有時(shí),讀寫(xiě)鎖才可能是有意義的。
檢測(cè)緩存到緩存的傳輸或錯(cuò)誤共享可能非常困難。一種技術(shù)是查看在執(zhí)行時(shí)間概要文件中引用的內(nèi)存和語(yǔ)句; 如果大多數(shù)加載沒(méi)有錯(cuò)過(guò)緩存,這種方法就可以很好地工作。對(duì)于開(kāi)發(fā)工具來(lái)說(shuō),需要與編譯器和運(yùn)行時(shí)數(shù)據(jù)收集進(jìn)行仔細(xì)的集成才能正確地解決這個(gè)問(wèn)題。

10. 沒(méi)有針對(duì)常見(jiàn)的情況進(jìn)行優(yōu)化
一般地,頻繁的操作比不頻繁的操作多出幾個(gè)數(shù)量級(jí),設(shè)計(jì)算法來(lái)利用這種不對(duì)稱性可以產(chǎn)生顯著的收益。一個(gè)簡(jiǎn)單的例子是使用哈希表鎖,這提高了對(duì)表進(jìn)行簡(jiǎn)單搜索、插入或刪除的可伸縮性,同時(shí)監(jiān)督需要訪問(wèn)整個(gè)表的操作,如調(diào)整大小。一個(gè)復(fù)雜的示例是鎖定內(nèi)核中的 CPU ,當(dāng)前 CPU 的鎖定實(shí)現(xiàn)利用了讀訪問(wèn)和寫(xiě)訪問(wèn)的巨大優(yōu)勢(shì),線程只需防止自己的搶占,這只需要一個(gè)本地內(nèi)存的引用。
常見(jiàn)場(chǎng)景和用例才是性能優(yōu)化的核心關(guān)注點(diǎn),對(duì)于應(yīng)用層的軟件更是如此。
小結(jié)
這10個(gè)問(wèn)題應(yīng)該有助于我們研究系統(tǒng)的性能設(shè)計(jì),至少能更快地認(rèn)識(shí)到這些問(wèn)題。盡管并非所有項(xiàng)目的性能都具有挑戰(zhàn)性,但是避免這些反模式將使有限的資源更加有效。其核心思想是,在項(xiàng)目開(kāi)始時(shí)在基準(zhǔn)、算法和數(shù)據(jù)結(jié)構(gòu)選擇方面所做的性能工作將在以后帶來(lái)巨大的好處。
【關(guān)聯(lián)閱讀】
