<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                在上一講中,我介紹了 Java 性能問題分析的一些基本思路。但在實際工作中,我們不能僅僅等待性能出現問題再去試圖解決,而是需要定量的、可對比的方法,去評估 Java 應用性能,來判斷其是否能夠符合業務支撐目標。今天這一講,我會介紹從 Java 開發者角度,如何從代碼級別判斷應用的性能表現,重點理解最廣泛使用的基準測試(Benchmark)。 今天我要問你的問題是,有人說“Lambda 能讓 Java 程序慢 30 倍”,你怎么看? 為了讓你清楚地了解這個背景,請參考下面的代碼片段。在實際運行中,基于 Lambda/Stream 的版本(lambdaMaxInteger),比傳統的 for-each 版本(forEachLoopMaxInteger)慢很多。 ~~~ // 一個大的 ArrayList,內部是隨機的整形數據 volatile List<Integer> integers = … // 基準測試 1 public int forEachLoopMaxInteger() { int max = Integer.MIN_VALUE; for (Integer n : integers) { max = Integer.max(max, n); } return max; } // 基準測試 2 public int lambdaMaxInteger() { return integers.stream().reduce(Integer.MIN_VALUE, (a, b) -> Integer.max(a, b)); } ~~~ ## 典型回答 我認為,“Lambda 能讓 Java 程序慢 30 倍”這個爭論實際反映了幾個方面: 第一,基準測試是一個非常有效的通用手段,讓我們以直觀、量化的方式,判斷程序在特定條件下的性能表現。 第二,基準測試必須明確定義自身的范圍和目標,否則很有可能產生誤導的結果。前面代碼片段本身的邏輯就有瑕疵,更多的開銷是源于自動裝箱、拆箱(auto-boxing/unboxing),而不是源自 Lambda 和 Stream,所以得出的初始結論是沒有說服力的。 第三,雖然 Lambda/Stream 為 Java 提供了強大的函數式編程能力,但是也需要正視其局限性: * 一般來說,我們可以認為 Lambda/Stream 提供了與傳統方式接近對等的性能,但是如果對于性能非常敏感,就不能完全忽視它在特定場景的性能差異了,例如:**初始化的開銷**。 Lambda 并不算是語法糖,而是一種新的工作機制,在首次調用時,JVM 需要為其構建[CallSite](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/CallSite.html)實例。這意味著,如果 Java 應用啟動過程引入了很多 Lambda 語句,會導致啟動過程變慢。其實現特點決定了 JVM 對它的優化可能與傳統方式存在差異。 * 增加了程序診斷等方面的復雜性,程序棧要復雜很多,Fluent 風格本身也不算是對于調試非常友好的結構,并且在可檢查異常的處理方面也存在著局限性等。 ## 考點分析 今天的題目是源自于一篇有爭議的[文章](https://blog.takipi.com/benchmark-how-java-8-lambdas-and-streams-can-make-your-code-5-times-slower/),原文后來更正為“如果 Stream 使用不當,會讓你的代碼慢 5 倍”。針對這個問題我給出的回答,并沒有糾結于所謂的“快”與“慢”,而是從工程實踐的角度指出了基準測試本身存在的問題,以及 Lambda 自身的局限性。 從知識點的角度,這個問題考察了我在[專欄第 7 講](http://time.geekbang.org/column/article/7514)中介紹過的自動裝箱 / 拆箱機制對性能的影響,并且考察了 Java 8 中引入的 Lambda 特性的相關知識。除了這些知識點,面試官還可能更加深入探討如何用基準測試之類的方法,將含糊的觀點變成可驗證的結論。 對于 Java 語言的很多特性,經常有很多似是而非的 “秘籍”,我們有必要去偽存真,以定量、定性的方式探究真相,探討更加易于推廣的實踐。找到結論的能力,比結論本身更重要,因此在今天這一講中,我們來探討一下: * 基準測試的基礎要素,以及如何利用主流框架構建簡單的基準測試。 * 進一步分析,針對保證基準測試的有效性,如何避免偏離測試目的,如何保證基準測試的正確性。 ## 知識擴展 首先,我們先來整體了解一下基準測試的主要目的和特征,專欄里我就不重復那些[書面的定義](https://baike.baidu.com/item/%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95)了。 性能往往是特定情景下的評價,泛泛地說性能“好”或者“快”,往往是具有誤導性的。通過引入基準測試,我們可以定義性能對比的明確條件、具體的指標,進而保證得到**定量的、可重復的**對比數據,這是工程中的實際需要。 不同的基準測試其具體內容和范圍也存在很大的不同。如果是專業的性能工程師,更加熟悉的可能是類似[SPEC](https://www.spec.org/)提供的工業標準的系統級測試;而對于大多數 Java 開發者,更熟悉的則是范圍相對較小、關注點更加細節的微基準測試(Micro-Benchmark)。我在文章開頭提的問題,就是典型的微基準測試,也是我今天的側重點。 **什么時候需要開發微基準測試呢?** 我認為,當需要對一個大型軟件的某小部分的性能進行評估時,就可以考慮微基準測試。換句話說,微基準測試大多是 API 級別的驗證,或者與其他簡單用例場景的對比,例如: * 你在開發共享類庫,為其他模塊提供某種服務的 API 等。 * 你的 API 對于性能,如延遲、吞吐量有著嚴格的要求,例如,實現了定制的 HTTP 客戶端 API,需要明確它對 HTTP 服務器進行大量 GET 請求時的吞吐能力,或者需要對比其他 API,保證至少對等甚至更高的性能標準。 所以微基準測試更是偏基礎、底層平臺開發者的需求,當然,也是那些追求極致性能的前沿工程師的最愛。 **如何構建自己的微基準測試,選擇什么樣的框架比較好?** 目前應用最為廣泛的框架之一就是[JMH](http://openjdk.java.net/projects/code-tools/jmh/),OpenJDK 自身也大量地使用 JMH 進行性能對比,如果你是做 Java API 級別的性能對比,JMH 往往是你的首選。 JMH 是由 Hotspot JVM 團隊專家開發的,除了支持完整的基準測試過程,包括預熱、運行、統計和報告等,還支持 Java 和其他 JVM 語言。更重要的是,它針對 Hotspot JVM 提供了各種特性,以保證基準測試的正確性,整體準確性大大優于其他框架,并且,JMH 還提供了用近乎白盒的方式進行 Profiling 等工作的能力。 使用 JMH 也非常簡單,你可以直接將其依賴加入 Maven 工程,如下圖: ![](https://img.kancloud.cn/0d/d2/0dd290f8842959cb02d6c3a434a58e68_465x253.png) 也可以,利用類似下面的命令,直接生成一個 Maven 項目。 ~~~ $ mvn archetype:generate \ -DinteractiveMode=false \ -DarchetypeGroupId=org.openjdk.jmh \ -DarchetypeArtifactId=jmh-java-benchmark-archetype \ -DgroupId=org.sample \ -DartifactId=test \ -Dversion=1.0 ~~~ JMH 利用注解(Annotation),定義具體的測試方法,以及基準測試的詳細配置。例如,至少要加上“@Benchmark”以標識它是個基準測試方法,而 BenchmarkMode 則指定了基準測試模式,例如下面例子指定了吞吐量(Throughput)模式,還可以根據需要指定平均時間(AverageTime)等其他模式。 ~~~ @Benchmark @BenchmarkMode(Mode.Throughput) public void testMethod() { // Put your benchmark code here. } ~~~ 當我們實現了具體的測試后,就可以利用下面的 Maven 命令構建。 ~~~ mvn clean install ~~~ 運行基準測試則與運行不同的 Java 應用沒有明顯區別。 ~~~ java -jar target/benchmarks.jar ~~~ 更加具體的上手步驟,請參考相關[指南](http://www.baeldung.com/java-microbenchmark-harness)。JMH 處處透著濃濃的工程師味道,并沒有糾結于完善的文檔,而是提供了非常棒的[樣例代碼](http://hg.openjdk.java.net/code-tools/jmh/file/3769055ad883/jmh-samples/src/main/java/org/openjdk/jmh/samples),所以你需要習慣于直接從代碼中學習。 **如何保證微基準測試的正確性,有哪些坑需要規避?** 首先,構建微基準測試,需要從白盒層面理解代碼,尤其是具體的性能開銷,不管是 CPU 還是內存分配。這有兩個方面的考慮,第一,需要保證我們寫出的基準測試符合測試目的,確實驗證的是我們要覆蓋的功能點,這一講的問題就是個典型例子;第二,通常對于微基準測試,我們通常希望代碼片段確實是有限的,例如,執行時間如果需要很多毫秒(ms),甚至是秒級,那么這個有效性就要存疑了,也不便于診斷問題所在。 更加重要的是,由于微基準測試基本上都是體量較小的 API 層面測試,最大的威脅來自于過度“聰明”的 JVM!Brain Goetz 曾經很早就指出了微基準測試中的[典型問題](https://www.ibm.com/developerworks/java/library/j-jtp02225/)。 由于我們執行的是非常有限的代碼片段,必須要保證 JVM 優化過程不影響原始測試目的,下面幾個方面需要重點關注: * 保證代碼經過了足夠并且合適的預熱。我在[專欄第 1 講](http://time.geekbang.org/column/article/6845)中提到過,默認情況,在 server 模式下,JIT 會在一段代碼執行 10000 次后,將其編譯為本地代碼,client 模式則是 1500 次以后。我們需要排除代碼執行初期的噪音,保證真正采樣到的統計數據符合其穩定運行狀態。 通常建議使用下面的參數來判斷預熱工作到底是經過了多久。 ~~~ -XX:+PrintCompilation ~~~ 我這里建議考慮另外加上一個參數,否則 JVM 將默認開啟后臺編譯,也就是在其他線程進行,可能導致輸出的信息有些混淆。 ~~~ -Xbatch ~~~ 與此同時,也要保證預熱階段的代碼路徑和采集階段的代碼路徑是一致的,并且可以觀察 PrintCompilation 輸出是否在后期運行中仍然有零星的編譯語句出現。 * 防止 JVM 進行無效代碼消除(Dead Code Elimination),例如下面的代碼片段中,由于我們并沒有使用計算結果 mul,那么 JVM 就可能直接判斷無效代碼,根本就不執行它。 ~~~ public void testMethod() { int left = 10; int right = 100; int mul = left * right; } ~~~ 如果你發現代碼統計數據發生了數量級程度上的提高,需要警惕是否出現了無效代碼消除的問題。 解決辦法也很直接,盡量保證方法有返回值,而不是 void 方法,或者使用 JMH 提供的[BlackHole](http://hg.openjdk.java.net/code-tools/jmh/file/3769055ad883/jmh-core/src/main/java/org/openjdk/jmh/infra/Blackhole.java)設施,在方法中添加下面語句。 ~~~ public void testMethod(Blackhole blackhole) { // … blackhole.consume(mul); } ~~~ * 防止發生常量折疊(Constant Folding)。JVM 如果發現計算過程是依賴于常量或者事實上的常量,就可能會直接計算其結果,所以基準測試并不能真實反映代碼執行的性能。JMH 提供了 State 機制來解決這個問題,將本地變量修改為 State 對象信息,請參考下面示例。 ~~~ @State(Scope.Thread) public static class MyState { public int left = 10; public int right = 100; } public void testMethod(MyState state, Blackhole blackhole) { int left = state.left; int right = state.right; int mul = left * right; blackhole.consume(mul); } ~~~ * 另外 JMH 還會對 State 對象進行額外的處理,以盡量消除偽共享([False Sharing](https://blogs.oracle.com/dave/java-contended-annotation-to-help-reduce-false-sharing))的影響,標記 @State,JMH 會自動進行補齊。 * 如果你希望確定方法內聯(Inlining)對性能的影響,可以考慮打開下面的選項。 ~~~ -XX:+PrintInlining ~~~ 從上面的總結,可以看出來微基準測試是一個需要高度了解 Java、JVM 底層機制的技術,是個非常好的深入理解程序背后效果的工具,但是也反映了我們需要審慎對待微基準測試,不被可能的假象蒙蔽。 我今天介紹的內容是相對常見并易于把握的,對于微基準測試,GC 等基層機制同樣會影響其統計數據。我在前面提到,微基準測試通常希望執行時間和內存分配速率都控制在有限范圍內,而在這個過程中發生 GC,很可能導致數據出現偏差,所以 Serial GC 是個值得考慮的選項。另外,JDK 11 引入了[Epsilon GC](http://openjdk.java.net/jeps/318),可以考慮使用這種什么也不做的 GC 方式,從最大可能性去排除相關影響。 今天我從一個爭議性的程序開始,探討了如何從開發者角度而不是性能工程師角度,利用(微)基準測試驗證你在性能上的判斷,并且介紹了其基礎構建方式和需要重點規避的風險點。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?我們在項目中需要評估系統的容量,以計劃和保證其業務支撐能力,談談你的思路是怎么樣的?常用手段有哪些?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看