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