轉(zhuǎn)帖|行業(yè)資訊|編輯:蔣永|2016-11-24 10:24:40.000|閱讀 303 次
概述:Martin Thompson是LMAX的聯(lián)合創(chuàng)始人,在QCon圣保羅2016上做過(guò)關(guān)于性能的keynote演講。他最初計(jì)劃的演講題目為“關(guān)于性能的神話(huà)與傳說(shuō)”,不過(guò)Thompson后來(lái)將演講命名為“十大性能錯(cuò)誤”,因?yàn)椤拔覀兌紩?huì)犯錯(cuò)誤,而且很容易就會(huì)出現(xiàn)錯(cuò)誤”。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷(xiāo)售中 >>
Martin Thompson是LMAX的聯(lián)合創(chuàng)始人,在QCon圣保羅2016上做過(guò)關(guān)于性能的keynote演講。他最初計(jì)劃的演講題目為“關(guān)于性能的神話(huà)與傳說(shuō)”,不過(guò)Thompson后來(lái)將演講命名為“十大性能錯(cuò)誤”,因?yàn)?ldquo;我們都會(huì)犯錯(cuò)誤,而且很容易就會(huì)出現(xiàn)錯(cuò)誤”。
下面列出了他在生產(chǎn)環(huán)境下所見(jiàn)到的性能錯(cuò)誤TOP10,并且還包含了如何避免的建議。
很多人抱怨他們的系統(tǒng)不夠快,并通過(guò)編寫(xiě)更好的算法和數(shù)據(jù)結(jié)構(gòu)來(lái)尋求幫助,Thompson認(rèn)為實(shí)際上“他們所需的僅僅就是進(jìn)行升級(jí)”。升級(jí)操作系統(tǒng)、JVM、CLR等等。不進(jìn)行升級(jí)的常見(jiàn)借口就是“在新版本中可能會(huì)有bug。”
為了避免這種狀況,可以進(jìn)行定期的持續(xù)集成和測(cè)試,這應(yīng)該是開(kāi)發(fā)流程的基礎(chǔ)組成部分。Thompson以一個(gè)實(shí)時(shí)系統(tǒng)進(jìn)行了例證,開(kāi)發(fā)人員針對(duì)新版本的數(shù)據(jù)庫(kù)進(jìn)行了測(cè)試,在所有的測(cè)試通過(guò)之后,他們就將其發(fā)布到了生產(chǎn)環(huán)境之中。
Thompson講述了某個(gè)系統(tǒng)的故事,這個(gè)系統(tǒng)是用來(lái)提供Web頁(yè)面的,它非常緩慢,開(kāi)發(fā)人員最初認(rèn)為是數(shù)據(jù)庫(kù)的問(wèn)題并試圖在這方面進(jìn)行調(diào)優(yōu)。但是當(dāng)他在系統(tǒng)上運(yùn)行profiler時(shí),發(fā)現(xiàn)在一個(gè)循環(huán)中,ORM被調(diào)用了7,000次,這才是頁(yè)面加載緩慢的罪魁禍?zhǔn)住.?dāng)這個(gè)循環(huán)的問(wèn)題修復(fù)之后,系統(tǒng)的響應(yīng)變得完全正常。這里學(xué)到的經(jīng)驗(yàn)就是“對(duì)系統(tǒng)進(jìn)行度量。如果系統(tǒng)是一個(gè)黑盒的話(huà),你就無(wú)法說(shuō)明時(shí)間都耗費(fèi)在了哪里。”
Thompson展現(xiàn)了一個(gè)基準(zhǔn)測(cè)試結(jié)果,它會(huì)執(zhí)行一項(xiàng)操作,該操作會(huì)對(duì)內(nèi)存(RAM)中1GB數(shù)組的所有l(wèi)ong型進(jìn)行求和。這里所耗費(fèi)的時(shí)間取決于內(nèi)存是如何訪(fǎng)問(wèn)的,如下面的表格所示:
這個(gè)基準(zhǔn)測(cè)試的結(jié)果顯示并非所有的內(nèi)存操作都是等價(jià)的,我們需要關(guān)注它是如何進(jìn)行處理的。Thompson認(rèn)為非常重要的一點(diǎn)在于了解各種數(shù)據(jù)結(jié)構(gòu)的性能,他指出對(duì)于2GB以上的場(chǎng)景,Java的HashMap要比.NET的Dictionary慢十倍以上。他還補(bǔ)充說(shuō),也有一些場(chǎng)景.NET要比Java慢得多。
盡管在很多場(chǎng)景中,內(nèi)存分配幾乎是沒(méi)有什么成本的,但是它們的回收卻并非如此,因?yàn)樵诿鎸?duì)大量的數(shù)據(jù)集時(shí),垃圾收集器需要更多的時(shí)間。當(dāng)分配大量的數(shù)據(jù)時(shí),緩存會(huì)被填滿(mǎn),較舊的數(shù)據(jù)會(huì)被舍棄,使得在數(shù)據(jù)操作上的效率變?yōu)?0ns/op而不是7ns/op,這里變慢了不止一個(gè)數(shù)量級(jí)。
盡管對(duì)于特定的算法來(lái)說(shuō),采用并行很有吸引力,但是它也有一些局限性和相關(guān)的開(kāi)銷(xiāo)。Thompson引用了“可擴(kuò)展性!但是其COST如何?”這篇論文,論文的作者通過(guò)引入COST(勝過(guò)單線(xiàn)程的配置,Configuration that Outperforms a Single Thread)對(duì)比了并行系統(tǒng)以及單線(xiàn)程的系統(tǒng),COST的定義如下:
在特定的平臺(tái)中,特定問(wèn)題的COST指的是優(yōu)于單線(xiàn)程方案所需的硬件配置。COST將系統(tǒng)的擴(kuò)展性與系統(tǒng)所引入的開(kāi)銷(xiāo)進(jìn)行了權(quán)衡,并指明了系統(tǒng)實(shí)際所能取得的性能,它們可能并沒(méi)有帶來(lái)實(shí)際的收益,卻增加了并行所引入了開(kāi)銷(xiāo)。
作者分析了各種數(shù)據(jù)并行系統(tǒng)的測(cè)量結(jié)果,并得出如下的結(jié)論:“很多的系統(tǒng)要么具有非常高的COST,通常會(huì)需要上百個(gè)核心,要么針對(duì)他們所報(bào)告的配置,其性能要比單線(xiàn)程方案更差。”
在這個(gè)話(huà)題中,Thompson指出,并行任務(wù)會(huì)有相關(guān)的通信和同步開(kāi)銷(xiāo),并且有些活動(dòng)本質(zhì)上要求是串行的,不能實(shí)現(xiàn)并行。按照Amdahl定律,如果系統(tǒng)中有5%的活動(dòng)需要串行,那么不管使用了多少個(gè)處理器,系統(tǒng)的速度提升最多只能達(dá)到20倍。
Thompson還提到了Neil J. Gunther在1993年所提出的通用可擴(kuò)展性定律(Universal Scalability Law,PDF),該定律指出在并行非共享系統(tǒng)(shared-nothing)中甚至?xí)嬖诟嗟木窒扌裕?dāng)所使用的處理器數(shù)量達(dá)到一定程度后,速度會(huì)出現(xiàn)下降,這取決于并發(fā)、競(jìng)爭(zhēng)以及同步的水平。(更多的細(xì)節(jié)可以參考如何量化可擴(kuò)展性這個(gè)頁(yè)面。)按照上述兩個(gè)規(guī)律所總結(jié)的速度與處理器數(shù)量之間的關(guān)系如下圖所示:
Thompson指出通過(guò)USL能夠看到性能的下降,這要?dú)w因于并行系統(tǒng)中組件之間進(jìn)行通信所消耗的成本:“在系統(tǒng)中,所投入資源越多,通信路徑也會(huì)隨之增多,這會(huì)使算法的效率降低。”
Thompson補(bǔ)充說(shuō),在構(gòu)建并行系統(tǒng)時(shí),主要的建議是避免共享可變(mutable)的狀態(tài),因?yàn)?ldquo;它非常難以進(jìn)行判斷……最終你會(huì)遇到很多的bug”。推薦的方式是要么采用非共享架構(gòu),要么針對(duì)特定的一塊數(shù)據(jù),只使用一個(gè)寫(xiě)入器。
對(duì)這個(gè)性能問(wèn)題,他的最終建議:如果你想提升算法的速度的話(huà),在嘗試并行方案之前,先設(shè)法提升單線(xiàn)程版本的性能,因?yàn)椴⑿蟹桨笇?shí)在是太難了。
針對(duì)這個(gè)話(huà)題,Thompson認(rèn)為很多在考慮微服務(wù)架構(gòu)的人對(duì)TCP并沒(méi)有充分的理解。在特定的場(chǎng)景中,有可能會(huì)遇到延遲的ACK,它會(huì)限制鏈路上所發(fā)送的數(shù)據(jù)包,每秒鐘只會(huì)有2-5個(gè)數(shù)據(jù)包。這是因?yàn)門(mén)CP兩個(gè)算法所引起的死鎖:Nagle以及TCP Delayed Acknowledgement。在200-500ms的超時(shí)之后,會(huì)打破這個(gè)死鎖,但是微服務(wù)之間的通信卻會(huì)分別受到影響。推薦的方案是使用TCP_NODELAY,它會(huì)禁用Nagle的算法,多個(gè)更小的包可以依次發(fā)送。按照Thompson的說(shuō)法,其中的差別在5到500 req/sec。
客戶(hù)端和服務(wù)器之間的同步通信會(huì)帶來(lái)時(shí)間的損耗,對(duì)于需要快速通信的系統(tǒng)來(lái)說(shuō),這會(huì)成為一個(gè)問(wèn)題。Thompson說(shuō),它的解決方案并不是購(gòu)買(mǎi)更加昂貴和快速的硬件,而是使用異步通信。在這種場(chǎng)景下,客戶(hù)端可以發(fā)送多個(gè)請(qǐng)求到服務(wù)器端,而不必等待它們之間的響應(yīng)。采用這種方式需要改變客戶(hù)端發(fā)送請(qǐng)求的方式,但這是值得的。
開(kāi)發(fā)人員很多時(shí)候會(huì)選擇使用文本編碼格式實(shí)現(xiàn)鏈路上的數(shù)據(jù)傳輸,比如JSON、XML或Base64,因?yàn)?ldquo;這對(duì)人類(lèi)是可讀的”。但是Thompson指出在兩個(gè)系統(tǒng)之間進(jìn)行對(duì)話(huà)的時(shí)候,是沒(méi)有人讀這些數(shù)據(jù)的。借助這種方式,使用簡(jiǎn)單的文本編輯器就能很容易地進(jìn)行調(diào)試,但是在將二進(jìn)制數(shù)據(jù)與文本之間進(jìn)行互相轉(zhuǎn)換的時(shí)候,這會(huì)帶來(lái)很高的CPU損耗。該問(wèn)題的解決方案是使用能夠理解二進(jìn)制的更好的工具,Thompson提到了Wireshark。
按照Thompson的說(shuō)法,有一些與性能相關(guān)的最負(fù)面影響是由API引起的。它使用如下的代碼來(lái)闡述較差的代碼簽名:
public void startElement( String uri, String localName, String qName, Attributes atts) throws SAXException
描述:
在處理XML的時(shí)候,通常我們并不會(huì)使用這些值[三個(gè)String以及屬性的集合]。我們分配了很多的內(nèi)容,但是卻將其浪費(fèi)并拋棄掉了。這會(huì)損耗電池的壽命,白白地浪費(fèi)資源。我們需要使其更加簡(jiǎn)單一些。
他建議采用如下的簽名,實(shí)現(xiàn)更加簡(jiǎn)單的方法:
public void characters( char[] ch, int start, int length) throws SAXException
有些人可能會(huì)抱怨后面的這個(gè)方法要比前一個(gè)更難用,Thompson建議采用組合的方式,將其中一個(gè)用另一個(gè)封裝起來(lái),這樣的話(huà),能夠給用戶(hù)多一個(gè)選擇。如果性能不是什么問(wèn)題的話(huà),可以采用第一種(使用String),否則的話(huà),第二個(gè)方案會(huì)更好一些。
Thompson提到的第二個(gè)樣例是字符串拆分:
public String[] split(String regex)
這個(gè)方法簽名相關(guān)的性能問(wèn)題包括:
更好的方案是使用Iterable,它能夠避免在內(nèi)存中創(chuàng)建中間狀態(tài)的token副本:
public Iterable split(String regex)
另外一種方案是允許調(diào)用者提供存儲(chǔ)token的集合。如果調(diào)用者想要對(duì)token列表去重的話(huà),應(yīng)該傳遞一個(gè)Set進(jìn)來(lái),如果想得到有序列表的話(huà),就需要傳遞一個(gè)TreeMap進(jìn)來(lái):
public void split( String regex, Collection dst)
Thompson所列的排名第一的性能問(wèn)題是寫(xiě)日志所耗費(fèi)的時(shí)間。他通過(guò)一個(gè)圖表展現(xiàn)了當(dāng)線(xiàn)程數(shù)增加的時(shí)候,日志操作所耗費(fèi)的平均時(shí)間:
這個(gè)圖顯示了一個(gè)100%的順序操作,不管使用多少線(xiàn)程來(lái)記錄日志,所需的時(shí)間均呈線(xiàn)性增長(zhǎng)。Thompson說(shuō)大多數(shù)已有的日志系統(tǒng)都可以得出這樣一幅圖表,“Logger是系統(tǒng)中最大的瓶頸之一”。這個(gè)問(wèn)題的解決方案是使用異步的Logger。
另外,Logger所記錄的數(shù)據(jù)應(yīng)該是結(jié)構(gòu)化的數(shù)據(jù),便于后續(xù)的工具進(jìn)行讀取和處理,而不應(yīng)該是一堆String。如果是記錄重復(fù)的錯(cuò)誤,他建議在錯(cuò)誤第一次出現(xiàn)的時(shí)候進(jìn)行記錄,后續(xù)出現(xiàn)時(shí)只需對(duì)一個(gè)計(jì)數(shù)器進(jìn)行遞增,告知對(duì)應(yīng)的錯(cuò)誤出現(xiàn)了多少次即可。對(duì)于實(shí)時(shí)系統(tǒng)的調(diào)試,Thompson建議使用代碼編織(code weaver)的技術(shù),如Byte Buddy,因?yàn)樗軌虮苊饩帉?xiě)和運(yùn)行不必要的日志代碼。
>>>>>查看更多測(cè)試分析相關(guān)資訊、產(chǎn)品
活動(dòng)時(shí)間:11月1日-11月30日
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn