凡事預則立,不預則廢,和許多事情一樣,java性能調優的成功,離不開行動計劃、方法或政策以及特定的領域背景知識。為了在java性能調優工作中有所成就,你得超越“花似霧中看”的狀态,進入“悠然見南山”或者已然是“一覽衆山小”的境界。
這三個境界的說法可能讓你有些糊塗吧,下面進一步解釋。
花似霧中看(i don‘t know what i don‘t know)。有時候下達的任務會涉及你所不熟悉的問題域。了解陌生問題域首先面臨的困難就是如何竭盡所能地學會它,因為你對它幾乎一無所知。對于這類問題域,你有許多東西不了解,或者不知道重點。換句話說,這個問題域有哪些東西需要了解,你還傻傻看不清楚。這個階段就是“花似霧中看”。
悠然見南山(i know what i don‘t know)。剛進入不熟悉的問題域時,你對它知之甚少,随着時間的推移,你對它的許多重要方面都已有所認識,隻是對重要的具體細節還缺乏了解。這時,你可以算是剛剛“見南山”。
一覽衆山小(i already know what i need to know)。還有些時候,你對任務的問題域非常熟悉,或者已經具有該領域所必備的技能和知識,是這方面的專家。或者你對問題域足夠了解,處理起來得心應手,比如你已經掌握了必要的知識,解決問題遊刃有餘。如果達到這個境界,那就意味着你已經是“一覽衆山小”了。
通常認為,傳統的軟體開發過程主要包括4個階段:分析、設計、編碼和測試,如圖1-1所示。
圖1-1 傳統軟體開發過程
替換圖字: start:開始; analysis:分析; design:設計; code:編碼; test:測試; quality:品質合格?; yes:是; no:否; deploy:部署
分析是開發過程的第一步,用于評估需求、權衡各種架構的利弊以及構思高層抽象。設計則依據分析階段的基本架構和高層抽象,進行更精細的抽象并着手考慮具體實作。編碼自然就是設計的實作。編碼之後是測試,用以驗證實作是否合乎應用需求。值得注意的是,測試階段通常隻包括功能測試,即檢驗應用的執行是否合乎需求規格。一旦測試完成,應用就可以釋出給客戶了。
遵循這種傳統軟體開發過程的應用,通常要到測試或即将釋出時才會關注性能或擴充性。為了解決這個問題,wilson和kesselman對傳統軟體開發過程做了些補充,在傳統開發模型基礎上引入了性能測試分析階段,參見他們的暢銷書java platform performance。他們建議在測試階段之後增加性能測試,并将“性能測試是否通過”設定為産品是否釋出的标準。如果達到性能和擴充性标準,應用就可以釋出,否則就要轉向性能分析,并依據分析結果回到之前的某個或者某些步驟。換句話說,通過性能分析來定位性能問題。wilson和kesselman添加的性能測試分析如圖1-2所示。
圖1-2 wilson和kesselman添加性能測試分析之後的軟體開發過程
替換圖字: start:開始; analysis:分析; design:設計; code:編碼; performance test:性能測試; performance acceptable:性能測試是否通過; profile:性能分析; yes:是; no:否; deploy:部署
對分析階段提煉出來的性能需求,wilson和kesselman建議以用例(use case)的方式特别辨別出來,這有助于在分析階段制定性能評估名額。不過應用的需求文檔中通常都不會明确描述性能或擴充性需求。如果你正在開發的應用還沒有明确定義這些需求,那就應該想辦法将它們挖掘出來。拿吞吐量和延遲性需求舉例,以下清單列舉了挖掘這些需求所要考慮的問題。
應用預期的吞吐量是多少?
請求和響應之間的延遲預期是多少?
應用支援多少并發使用者或者并發任務?
當并發使用者數或并發任務數達到最大時,可接受的吞吐量和延遲是多少?
最差情況下的延遲是多少?
要使垃圾收集引入的延遲在可容忍範圍之内,垃圾收集的頻率應該是多少?
需求和對應的用例文檔應該回答上述問題,并以此制定基準測試和性能測試,確定應用能夠滿足性能和擴充性需求。基準測試和性能測試應該在性能測試階段執行。評估用例時有些用例的風險過高,難以實作,應該在分析階段後期,通過一些原型、基準測試和微基準測試來降低此類風險。分析結束後再變更決策的代價非常高,這個方法可以讓你事先對決策進行評估。軟體開發周期中的軟體缺陷、低劣設計和糟糕實作發現得越晚,修複的代價就越大,這是一條颠撲不破的金科玉律。降低用例的高風險有助于避免這些代價昂貴的錯誤。
現在許多應用在開發過程中都會使用自動建構和測試。wilson和kesselman建議改進軟體開發過程,在自動建構或測試中進一步添加自動性能測試。自動性能測試可以發出通知,比如用電子郵件将性能測試結果(如性能是衰減還是改善,或性能名額的達成度)發送給幹系人。這個過程可以将因不滿足應用性能名額而失敗的測試,以及測試的統計資料自動記錄到追蹤系統。
将性能測試內建到自動建構過程中後,每次代碼變更送出到源代碼庫時,都能很容易地追蹤因變更而導緻的性能變化,也就能在軟體開發的早期發現性能衰減。
另外,将統計方法和自動統計分析添加到自動性能測試系統中也值得考慮。運用統計方法可以進一步驗證性能測試的結果。
自頂向下和自底向上是兩種常用的性能分析方法。顧名思義,自頂向下(top down)着眼于應用頂層,從上往下尋找軟體棧中的優化機會和問題。相反,自底向上(bottom
up)則從軟體棧最底層的cpu統計資料(例如cpu緩存未命中率、cpu指令效率)開始,逐漸上升到應用自身的結構或該應用常見的使用方式。應用開發人員常常使用自頂向下的方法,而性能問題專家則通常采用自底向上的方法,用以辨識因不同硬體架構、作業系統或不同的java虛拟機實作所導緻的性能差異。如你所想,不同方法可以用來查找不同類型的性能問題。
自頂向下大概是最常用的性能調優方法。如果需要更改應用軟體棧的頂層代碼進行調優,這也是最常用的方法。
使用自頂向下的方法時,通常你需要從幹系人發現性能問題的負載開始監控應用。應用的配置變化或日常負荷變化可能導緻性能降低,這種情況下,需要持續地監控應用。此外,當應用的性能和擴充性需求發生變化時,應用可能無法滿足新的要求,這時也需要監控應用程式的性能。
不管何種原因引起的性能調優,自頂向下的第一步總是對運作在特定負載之下的應用進行監控。監控的範圍包括作業系統、java虛拟機、java ee容器以及應用的性能測量統計名額。基于監控資訊所給出的提示再開展下一步工作,例如jvm垃圾收集器調優、jvm指令行參數調優、作業系統調優,或者應用程式性能分析。性能分析可能導緻應用程式的更改,或者發現第三方庫或java se類庫在實作上的不足。
在不同平台(指底層的cpu架構和數量不同)上進行應用性能調優時,性能專家常使用自底向上的方法。将應用遷移到其他作業系統上時,也常用這種方法改善性能。在無法更改應用源代碼時,例如應用已經部署在生産環境中,或者系統供應商為了在競争中占得先機而必須将性能發揮到極緻,也常常會使用這種方法。
自底向上需要收集和監控最底層cpu的性能統計資料。監控的cpu統計資料包括執行特定任務所需要的cpu指令數(通常稱為路徑長度,path length),以及應用在一定負載下運作時的cpu緩存未命中率。雖然還有其他重要的cpu統計資料,但這兩項是自底向上中最常用的。在一定負載下,應用執行和擴充所需的cpu指令越少,運作得就越快。降低cpu緩存未命中率也能改善應用的性能,因為cpu緩存失效會導緻cpu為了等待從記憶體擷取資料而浪費若幹個周期,而降低cpu緩存未命中率,意味着cpu可以減少等待記憶體資料的時間,應用也就能運作得更快。
自底向上關注的通常是在不更改應用的前提下,改善cpu使用率。假如應用可以更改,自底向上也能為如何修改應用提供建議。這些更改包括應用源代碼的變動,如将經常使用的資料移到一起,使得通路同一條cpu緩存行(cpu cache line)就能擷取這些資料,而不用等待從記憶體中擷取資料。這個改動可以降低cpu緩存未命中率,進而減少cpu等待記憶體資料的時間。
現代java虛拟機內建了成熟的jit編譯器,可以在java應用的執行過程中進行優化,比如依據應用的記憶體通路模式或應用特定的代碼路徑,生成更有效的機器碼。也可以調整作業系統的設定來改善性能,例如更改cpu排程算法,或者修改作業系統的等待時間(指作業系統在将應用執行線程遷移到其他cpu硬體線程之前所等待的時間)。
如果你覺得可以用自底向上的方法,那應該先從收集作業系統和jvm的統計資料開始。監控這些統計資料可以為下一步應該關注哪些重點提供線索。
本文内容摘自《