- Java性能權(quán)威指南
- (美)Scott Oaks
- 3541字
- 2020-05-06 16:35:19
1.3 全面的性能調(diào)優(yōu)
本書關(guān)注于如何以最佳方式利用JVM和Java平臺(tái)API,讓程序運(yùn)行得更快。但除了這兩點(diǎn),還有許多外在的因素影響性能。書中這些因素時(shí)不時(shí)會(huì)出現(xiàn),但因?yàn)樗鼈儾恢挥绊慗ava,所以不會(huì)深入討論。JVM和Java平臺(tái)的性能只是高性能主題中的一小部分。
本書會(huì)覆蓋一些外部因素,這些因素的重要性不亞于Java的性能調(diào)優(yōu)。本書中基于Java的調(diào)優(yōu)方法可以和這些因素相互補(bǔ)充,但這些因素多數(shù)已經(jīng)超過了本書討論的范圍。
1.3.1 編寫更好的算法
Java的許多細(xì)節(jié)和性能標(biāo)志都可以影響應(yīng)用的性能,只不過從來都沒有一個(gè)叫-XX:+RunReallyFast的神奇標(biāo)志。
歸根結(jié)底,應(yīng)用的性能取決于它的代碼如何編寫。例如,如果程序循環(huán)遍歷數(shù)組中的所有元素,JVM就可以優(yōu)化數(shù)組的邊界檢查,使循環(huán)更快,展開循環(huán)能提供額外的加速。但如果循環(huán)是為了找到特定元素,那目前還沒有什么優(yōu)化的辦法,使得遍歷數(shù)組和采用HashMap的版本一樣快。
需要更高性能時(shí),算法是否優(yōu)秀就是重中之重了。
1.3.2 編寫更少的代碼
有些人寫代碼是為錢,有些是為樂趣,還有些人將代碼回饋社區(qū),但不管怎樣,大家都是碼農(nóng)(或者在寫程序的團(tuán)隊(duì)里工作)。很難想象,我們對(duì)項(xiàng)目的貢獻(xiàn)是少寫代碼,因?yàn)槿匀挥泄芾碚咄ㄟ^所寫的代碼量來評(píng)估開發(fā)人員的績(jī)效。
我能理解這種想法,不過這種想法與現(xiàn)實(shí)并不吻合。同樣是正確的程序,小程序運(yùn)行起來要比大程序快。對(duì)所有的計(jì)算機(jī)程序來說都是如此,Java程序自然也不例外。要編譯的代碼越多,等待程序啟動(dòng)所耗費(fèi)的時(shí)間就越長(zhǎng);要?jiǎng)?chuàng)建和銷毀的對(duì)象越多,垃圾收集的工作量就越大;要分配和持有的對(duì)象越多,GC的周期就越長(zhǎng);要從磁盤裝載進(jìn)JVM的類越多,程序啟動(dòng)所花費(fèi)的時(shí)間就越長(zhǎng);要執(zhí)行的代碼越多,機(jī)器硬件緩存的效率就越低;而執(zhí)行的代碼越多,花費(fèi)的時(shí)間就越長(zhǎng)。
無法取勝的戰(zhàn)爭(zhēng)
與直覺相反(和令人沮喪)的是,所有應(yīng)用的性能都會(huì)隨著時(shí)間,即應(yīng)用新版本的發(fā)布而降低。但由于硬件的改善使得新程序的運(yùn)行速度可以被接受,所以通常都不會(huì)有人注意到性能上的差異。
想象一下,在曾經(jīng)運(yùn)行Windows 95的機(jī)器上運(yùn)行Windows Aero界面,會(huì)是什么樣子?我以前喜歡Mac Quadra 950,但它無法運(yùn)行Mac OS X(如果真這么做了,它將比Mac OS 7.5慢許多許多)。從更小的層次上看,F(xiàn)irefox 23.0比Firefox 22.0快,但它們之間的版本差別很小。具有按tab頁(yè)瀏覽、同步滾動(dòng)和安全特性的Firefox要比之前的Mosaic強(qiáng)大,但Mosaic從我硬盤里裝載基本HTML文件的速度比Firefox 23.0快50%。
當(dāng)然,Mosaic幾乎不能從任何的熱門網(wǎng)站上裝載實(shí)際的URL,所以不太可能把Mosaic作為主要的瀏覽器。一般來說,特別是在兩個(gè)小版本之間,代碼會(huì)進(jìn)行優(yōu)化,從而運(yùn)行得更快。性能優(yōu)化工程師應(yīng)該注意到這點(diǎn)。如果我們擅長(zhǎng)這份工作,那就能贏得這場(chǎng)戰(zhàn)斗。這是美好而有意義的事。我認(rèn)為我們應(yīng)該改善現(xiàn)有應(yīng)用的性能。
但鐵一般的事實(shí)是:隨著新特性的添加和新要求的采納(為了與對(duì)手競(jìng)爭(zhēng)),程序會(huì)越來越大,越來越慢。
我把這總結(jié)為“積少成多”原則。開發(fā)人員總爭(zhēng)辯說,只是增加了很小的功能,壓根就不會(huì)有什么時(shí)間損耗(特別是不使用該功能的時(shí)候)。接著項(xiàng)目中的其他開發(fā)人員也同樣拍著胸脯保證,結(jié)果卻發(fā)現(xiàn)性能突然下降了好幾個(gè)百分點(diǎn)。下次發(fā)布的時(shí)候又重復(fù)出現(xiàn)這樣的情景,而此時(shí)程序性能已經(jīng)下降了10%,反復(fù)幾次這樣的過程之后,性能測(cè)試就會(huì)檢測(cè)到資源瓶頸——內(nèi)存使用達(dá)到臨界點(diǎn)、代碼緩存溢出等情況。對(duì)于這些情形,常規(guī)的性能測(cè)試可以捕獲發(fā)生狀況的原因,性能調(diào)優(yōu)小組也可以修正主要的性能衰減。但隨著時(shí)間的推移,小衰減積少成多,會(huì)越來越難以修復(fù)。
我并不是在鼓吹永遠(yuǎn)不要為產(chǎn)品增加新特性或者新代碼,很顯然增強(qiáng)程序是有利可圖的。但你得小心權(quán)衡,盡可能提高效能。
1.3.3 老調(diào)重彈的過早優(yōu)化
“過早優(yōu)化”一詞公認(rèn)是由高德納發(fā)明的,開發(fā)人員常常據(jù)此宣稱:只有在運(yùn)行時(shí)才能知道代碼的性能有多要緊。但你可能從來沒注意到,完整的原話是“我們不應(yīng)該把大量時(shí)間都耗費(fèi)在那些小的性能改進(jìn)上;過早考慮優(yōu)化是所有噩夢(mèng)的根源”。
這句名言的重點(diǎn)是,最終你應(yīng)該編寫清晰、直接、易讀和易理解的代碼。這里的“優(yōu)化”應(yīng)該理解為雖然算法和設(shè)計(jì)改變了復(fù)雜程序的結(jié)構(gòu),但是提供了更好的性能。那些真正的優(yōu)化最好留到以后,等到性能分析表明這些措施有巨大收益的時(shí)候才進(jìn)行。
而這里所指的過早優(yōu)化,并不包括避免那些已經(jīng)知道對(duì)性能不好的代碼結(jié)構(gòu)。每行代碼,如果有兩種簡(jiǎn)單、直接的編程方式,那就應(yīng)該選擇性能更好的那種。
在某種程度上,有經(jīng)驗(yàn)的Java開發(fā)人員都能很好地領(lǐng)會(huì)到這點(diǎn)(這也是一個(gè)例證,說明他們?nèi)辗e月累而掌握了調(diào)優(yōu)藝術(shù))。思考以下代碼:
log.log(Level.FINE, "I am here, and the value of X is" +calcX()+" and Y is "+calcY());
代碼包含了一個(gè)看起來不太必要的字符串連接。因?yàn)槌侨罩炯?jí)別很高,否則字符串的信息并不會(huì)記錄到日志中,如果不打印日志消息,那就沒必要調(diào)用calcX()和calcY()。有經(jīng)驗(yàn)的Java開發(fā)人員會(huì)下意識(shí)地避免這種寫法。有些IDE(例如NetBeans)會(huì)在代碼上打標(biāo)記并建議更改。(然而沒有完美的工具:NetBeans會(huì)在字符串連接操作上打標(biāo)記,卻不會(huì)建議去掉不必要的方法調(diào)用。)
像這樣的日志代碼會(huì)更好:
if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "I am here, and the value of X is {} and Y is {}", new Object[]{calcX(), calcY()}); }
除非啟用了日志功能,否則就可以在避免字符串連接(消息體中有格式化字符,不會(huì)提高性能,但使代碼更清晰)的同時(shí),避免方法調(diào)用或者對(duì)象分配。
這樣寫出來的代碼仍然清晰易讀,與原來的代碼相比,沒有太多額外工作。好吧,我們還是需要多敲幾下鍵盤,多加一行邏輯。不過這仍然不屬于應(yīng)該避免的過早優(yōu)化,它是好碼農(nóng)所熟悉的選擇。在你思考如何寫代碼的時(shí)候,請(qǐng)不要生搬硬套前輩們的教條。
本書中我們還會(huì)看到其他例子,例如第9章討論了處理Vector前先進(jìn)行循環(huán)的性能。
1.3.4 其他:數(shù)據(jù)庫(kù)很可能就是瓶頸
如果你開發(fā)的是獨(dú)立運(yùn)行不使用外部資源的Java應(yīng)用,性能就(幾乎)只與應(yīng)用本身相關(guān)。一旦添加了外部資源(例如數(shù)據(jù)庫(kù)),那這兩者的性能就都很重要了。在分布式環(huán)境中,比如Java EE應(yīng)用服務(wù)器、負(fù)載均衡器、數(shù)據(jù)庫(kù)和后臺(tái)企業(yè)信息系統(tǒng),Java應(yīng)用服務(wù)器的性能問題可能只是其中很小的部分。
本書并不關(guān)注整體系統(tǒng)的性能。對(duì)于整體系統(tǒng),我們需要采取結(jié)構(gòu)化方法針對(duì)系統(tǒng)的所有方面分析性能。CPU使用率、I/O延遲、系統(tǒng)整體的吞吐量都必須測(cè)量和分析。只有到那時(shí),我們才能判定到底是哪個(gè)組件導(dǎo)致了性能瓶頸。關(guān)于這個(gè)主題有大量?jī)?yōu)秀的資源,相關(guān)的方法和工具也不只針對(duì)Java。假定你已經(jīng)完成了分析,并且判斷出是運(yùn)行環(huán)境中Java組件的性能需要改善。
不只JVM有bug和性能問題
這節(jié)以數(shù)據(jù)庫(kù)的性能為例,但運(yùn)行環(huán)境的任何部分都可能會(huì)引起性能問題。
我曾經(jīng)遇到過一個(gè)問題,客戶正在安裝新版本的應(yīng)用服務(wù)器,而測(cè)試顯示請(qǐng)求發(fā)送到服務(wù)器上的時(shí)間變得越來越長(zhǎng)。于是我根據(jù)奧卡姆剃刀原則(參見下一條貼士),考察應(yīng)用服務(wù)器中所有可能產(chǎn)生問題的部分。
逐一排除之后,性能問題依舊,而且我也沒發(fā)現(xiàn)后臺(tái)數(shù)據(jù)庫(kù)有問題。因此最可能的原因是測(cè)試框架,通過性能分析判定負(fù)載發(fā)生器——Apache JMeter——才是性能衰退的原因。它將每個(gè)響應(yīng)保留在列表中,每次有新響應(yīng)到來時(shí),它都要遍歷整個(gè)列表,以便找到響應(yīng)時(shí)間90%的請(qǐng)求(如果不熟悉這些詞,請(qǐng)參見第2章)。
部署應(yīng)用的系統(tǒng),它的任何部分都可能會(huì)引起性能問題。常規(guī)案例分析建議應(yīng)該首先考慮系統(tǒng)最新變動(dòng)的部分(通常是JVM中的應(yīng)用),但仍然要準(zhǔn)備檢查環(huán)境的每一個(gè)可能出現(xiàn)問題的組件。
另一方面,不要忽視初步分析。如果數(shù)據(jù)庫(kù)是瓶頸(提示:的確是的話),那么無論怎么優(yōu)化訪問數(shù)據(jù)庫(kù)的Java應(yīng)用,都無助于整體性能;實(shí)際上可能適得其反。作為一般性原則,系統(tǒng)負(fù)載增加越大,系統(tǒng)性能就會(huì)越糟糕。如果更改了Java應(yīng)用使得它更有效,這只會(huì)增加已經(jīng)過載的數(shù)據(jù)庫(kù)的負(fù)載,整體性能實(shí)際反而會(huì)下降。導(dǎo)致的風(fēng)險(xiǎn)是,可能會(huì)得出錯(cuò)誤結(jié)論,即認(rèn)為不應(yīng)該改進(jìn)JVM。
增加系統(tǒng)某個(gè)組件的負(fù)載從而導(dǎo)致整個(gè)系統(tǒng)性能變慢,這項(xiàng)原則不僅限于數(shù)據(jù)庫(kù)。CPU密集型的應(yīng)用服務(wù)器增加負(fù)載,或者越來越多線程試圖獲取已經(jīng)有線程等待的鎖,還有許多其他場(chǎng)景,也都適用這項(xiàng)原則。第9章展示了一個(gè)僅涉及JVM的極端例子。
1.3.5 常見的優(yōu)化
如果所有的性能問題同等重要,從而“積少成多”地改進(jìn)性能,那是多么吸引人。但常見的用例場(chǎng)景才是真正應(yīng)該關(guān)注的重點(diǎn)。
我們可以從以下幾方面闡述這條原則。
? 借助性能分析來優(yōu)化代碼,重點(diǎn)關(guān)注性能分析中最耗時(shí)的操作。然而請(qǐng)注意,這并不意味著只看性能分析中的葉子方法(參見第3章)。
? 利用奧卡姆剃刀原則診斷性能問題。性能問題最可能的原因應(yīng)該是最容易解釋的:新代碼比機(jī)器配置更可能引入性能問題,而機(jī)器配置比JVM或者操作系統(tǒng)的bug更容易引入性能問題。隱藏的bug確實(shí)存在,但不應(yīng)該把最可能引起性能問題的原因首先歸咎于它,而只在測(cè)試用例通過某種方式觸發(fā)了隱藏的bug時(shí)才關(guān)注。但不應(yīng)該一上來就跳到這種不太可能的場(chǎng)景。
? 為應(yīng)用中最常用的操作編寫簡(jiǎn)單算法。以估算數(shù)學(xué)公式的程序?yàn)槔脩艨梢詻Q定他所期望的最大容許誤差為10%或1%。如果10%的誤差適合多數(shù)用戶,那么優(yōu)化代碼就意味著即便誤差范圍縮小為1%,但是速度變慢了。
- Mastering Python Scripting for System Administrators
- Getting Started with CreateJS
- Oracle數(shù)據(jù)庫(kù)從入門到運(yùn)維實(shí)戰(zhàn)
- Oracle BAM 11gR1 Handbook
- Test-Driven Machine Learning
- C++程序設(shè)計(jì)
- 分布式數(shù)據(jù)庫(kù)HBase案例教程
- ASP.NET Core and Angular 2
- MongoDB Administrator’s Guide
- Building a Media Center with Raspberry Pi
- 一步一步學(xué)Spring Boot:微服務(wù)項(xiàng)目實(shí)戰(zhàn)(第2版)
- 透視C#核心技術(shù):系統(tǒng)架構(gòu)及移動(dòng)端開發(fā)
- Extending Docker
- 區(qū)塊鏈:技術(shù)與場(chǎng)景
- Splunk Developer's Guide(Second Edition)