<a name="bdprtz"></a>html
JMH 是 Java Microbenchmark Harness 的縮寫。中文意思大體是 「JAVA 微基準測試套件」。首先先明白什麼是「基準測試」。百度百科給的定義以下:java
基準測試是指經過設計科學的測試方法、測試工具和測試系統,實現對一類測試對象的某項性能指標進行定量的和可對比的測試。git
能夠簡單的類比成咱們電腦經常使用的魯大師,或者手機經常使用的跑分軟件安兔兔之類的性能檢測軟件。都是按必定的基準或者在特定條件下去測試某一對象的的性能,好比顯卡、IO、CPU之類的。github
<a name="9v8igk"></a>web
基準測試的特質有以下幾種:數據庫
①、可重複性:可進行重複性的測試,這樣作有利於比較每次的測試結果,獲得性能結果的長期變化趨勢,爲系統調優和上線前的容量規劃作參考。bash
②、可觀測性:經過全方位的監控(包括測試開始到結束,執行機、服務器、數據庫),及時瞭解和分析測試過程發生了什麼。服務器
③、可展現性:相關人員能夠直觀明瞭的瞭解測試結果(web界面、儀表盤、折線圖樹狀圖等形式)。app
④、真實性:測試的結果反映了客戶體驗到的真實的狀況(真實準確的業務場景+與生產一致的配置+合理正確的測試方法)。maven
⑤、可執行性:相關人員能夠快速的進行測試驗證修改調優(可定位可分析)。
可見要作一次符合特質的基準測試,是很繁瑣也很困難的。外界因素很容易影響到最終的測試結果。特別對於 JAVA的基準測試。<br /> <br />有些文章會告訴咱們 JAVA是 C++編寫的,通常來講 JAVA編寫的程序不太可能比 C++編寫的代碼運行效率更好。可是JAVA在某些場景的確要比 C++運行的更高效。不要以爲天方夜譚。其實 JVM隨着這些年的發展已經變得很智能,它會在運行期間不斷的去優化。<br /> <br />這對於咱們程序來講是好事,可是對於性能測試就頭疼的。你運行的次數與時間不一樣可能得到的結果也不一樣,很難得到一個比較穩定的結果。對於這種狀況,有一個解決辦法就是大量的重複調用,而且在真正測試前還要進行必定的預熱,使結果儘量的準確。
除了這些,對於結果咱們還須要一個很好的展現,可讓咱們經過這些展現結果判斷性能的好壞。
**而這些JMH都有!**😊
<a name="6yfwmc"></a>
下面咱們以字符串拼接的幾種方法爲例子使用JMH作基準測試。
<a name="g0v4hb"></a>
JMH是 JDK9自帶的,若是你是 JDK9 以前的版本也能夠經過導入 openjdk
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.19</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.19</version> </dependency>
<a name="hqfcgh"></a>
. ├── pom.xml └── src ├── main │ └── java │ └── cn │ └── coder4j │ └── study │ └── demo │ └── jmh │ ├── benchmark │ │ └── StringConnectBenchmark.java │ └── runner │ └── StringBuilderRunner.java └── test └── java └── cn └── coder4j └── study └── demo
<a name="kzg1rm"></a>
/** * coder4j.cn * Copyright (C) 2013-2018 All Rights Reserved. */ package cn.coder4j.study.demo.jmh.runner; import cn.coder4j.study.demo.jmh.benchmark.StringConnectBenchmark; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; /** * @author buhao * @version StringBuilderRunner.java, v 0.1 2018-12-25 09:53 buhao */ public class StringBuilderRunner { public static void main( String[] args ) throws RunnerException { Options opt = new OptionsBuilder() // 導入要測試的類 .include(StringConnectBenchmark.class.getSimpleName()) // 預熱5輪 .warmupIterations(5) // 度量10輪 .measurementIterations(10) .mode(Mode.Throughput) .forks(3) .build(); new Runner(opt).run(); } }
/** * coder4j.cn * Copyright (C) 2013-2018 All Rights Reserved. */ package cn.coder4j.study.demo.jmh.benchmark; import org.openjdk.jmh.annotations.Benchmark; /** * @author buhao * @version StringConnectBenchmark.java, v 0.1 2018-12-25 09:29 buhao */ public class StringConnectBenchmark { /** * 字符串拼接之 StringBuilder 基準測試 */ @Benchmark public void testStringBuilder() { print(new StringBuilder().append(1).append(2).append(3).toString()); } /** * 字符串拼接之直接相加基準測試 */ @Benchmark public void testStringAdd() { print(new String()+ 1 + 2 + 3); } /** * 字符串拼接之String Concat基準測試 */ @Benchmark public void testStringConcat() { print(new String().concat("1").concat("2").concat("3")); } /** * 字符串拼接之 StringBuffer 基準測試 */ @Benchmark public void testStringBuffer() { print(new StringBuffer().append(1).append(2).append(3).toString()); } /** * 字符串拼接之 StringFormat 基準測試 */ @Benchmark public void testStringFormat(){ print(String.format("%s%s%s", 1, 2, 3)); } public void print(String str) { } }
<a name="gspdgn"></a>
# Run progress: 93.33% complete, ETA 00:00:15 # Fork: 3 of 3 objc[12440]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/bin/java (0x106a7d4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x106af74e0). One of the two will be used. Which one is undefined. # Warmup Iteration 1: 747281.755 ops/s # Warmup Iteration 2: 924220.081 ops/s # Warmup Iteration 3: 1129741.585 ops/s # Warmup Iteration 4: 1135268.541 ops/s # Warmup Iteration 5: 1062994.936 ops/s Iteration 1: 1142834.160 ops/s Iteration 2: 1143207.472 ops/s Iteration 3: 1178363.827 ops/s Iteration 4: 1156408.897 ops/s Iteration 5: 1123123.829 ops/s Iteration 6: 1086029.992 ops/s Iteration 7: 1108795.147 ops/s Iteration 8: 1125522.731 ops/s Iteration 9: 1120021.744 ops/s Iteration 10: 1119916.181 ops/s Result "cn.coder4j.study.demo.jmh.benchmark.StringConnectBenchmark.testStringFormat": 1132633.183 ±(99.9%) 16252.303 ops/s [Average] (min, avg, max) = (1082146.355, 1132633.183, 1182418.648), stdev = 24325.684 CI (99.9%): [1116380.879, 1148885.486] (assumes normal distribution) # Run complete. Total time: 00:03:57 Benchmark Mode Cnt Score Error Units StringConnectBenchmark.testStringAdd thrpt 30 63728919.269 ± 906608.141 ops/s StringConnectBenchmark.testStringBuffer thrpt 30 112423521.098 ± 1157072.848 ops/s StringConnectBenchmark.testStringBuilder thrpt 30 110558976.274 ± 654163.111 ops/s StringConnectBenchmark.testStringConcat thrpt 30 44820009.200 ± 524305.660 ops/s StringConnectBenchmark.testStringFormat thrpt 30 1132633.183 ± 16252.303 ops/s
<a name="u9iswx"></a>
這個 runner 類的做用,就是啓動基準測試。<br /> <br />JMH 一般有兩種方式啓動,一種就是經過命令行使用 maven 命令執行。這種適合對於大型基準測試,像那些要運行不少不少次,而且運行的時間也很長的狀況下。你能夠直接打個 jar包,發到服務器上,敲個命令就不用管它,過幾十分鐘、幾小時、幾天的時間再回來看結果。
可是不少狀況下,咱們只是想簡單測試一個小功能,不必還要搞臺服務器去跑。因此 JMH 還提供了一種經過 Main方法運行的方式,就如上面代碼所示。
在 Main 方法中,經過 org.openjdk.jmh.runner.Runner 類去運行 org.openjdk.jmh.runner.options.Options 實例便可。這裏的重點在於 Options 對象的構建。官方提供了一個OptionsBuilder對象去構建。這個 Builder對象是流式的。它的經常使用方法及對應的註解形式以下:
方法名 | 參數 | 做用 | 對應註解 |
---|---|---|---|
include | 要運行基準測試類的簡單名稱 eg. StringConnectBenchmark | 指定要運行的基準測試類 | - |
exclude | 不要運行基準測試類的簡單名稱 eg. StringConnectBenchmark | 指定不要運行的基準測試類 | - |
warmupIterations | 預熱的迭代次數 | 指定預熱的迭代次數 | @Warmup |
warmupBatchSize | 預熱批量的大小 | 指定預熱批量的大小 | @Warmup |
warmupForks | 預熱模式:INDI,BULK,BULK_INDI | 指定預熱模式 | @Warmup |
warmupMode | 預熱的模式 | 指定預熱的模式 | @Warmup |
warmupTime | 預熱的時間 | 指定預熱的時間 | @Warmup |
measurementIterations | 測試的迭代次數 | 指定測試的迭代次數 | @Measurement |
measurementBatchSize | 測試批量的大小 | 指定測試批量的大小 | @Measurement |
measurementTime | 測試的時間 | 指定測試的時間 | @Measurement |
mode | 測試模式: Throughput(吞吐量), AverageTime(平均時間),SampleTime(在測試中,隨機進行採樣執行的時間),SingleShotTime(在每次執行中計算耗時),All | 指定測試的模式 | @BenchmarkMode |
<br />這個就是真正執行基準測試的類,這個類很像單元測試的類,每一個測試方法中寫上你要執行的測試代碼。只不過這裏把@Test換成了@Benchmark註解。<br /> <br />而加上了這個就指明這個方法是基準測試方法,當 Runner類的 Main方法運行時,它就會找這些被註解修飾的方法,再按指定的規則去進行基準測試。固然可能不一樣的方法有時候須要不一樣的規則,這個時間能夠經過上面方法對應的註解形式去單獨指定某個方法的規則便可。
<a name="1iwmey"></a>
<br />結果主要分紅三個部分。<br /> <br />第一部分以 「#Warmup Iteration。。。。」這種形式的內容。這代表每次預熱迭代的結果。<br /> <br />另外一部分以「Iteration。。。」形式內容,這代表每次基準測試迭代的結果。
最後一部分以「Result。。。」形式的內容,這就是全部迭代跑完最終的結果。第一段結果告訴了咱們最大值、最小值、平均值的信息。<br /> <br />而最最後的表格結構的信息纔是咱們分析的重點,可是它輸出的結果有點錯位,剛開始我一直在糾結 Error是± 906608.141表明什麼意思,google了一圈發現,Error它其實什麼都沒輸出,並且 Score 是63728919.269 ± 906608.141。我用表格排板了一下,解釋以下:
Benchmark | Mode | Cnt | Score | Error | Units |
---|---|---|---|---|---|
基準測試執行的方法 | 測試模式,這裏是吞吐量 | 運行多少次 | 分數 | 錯誤 | 單位 |
StringConnectBenchmark.testStringAdd | thrpt | 30 | 63728919.269 ± 906608.141 | ops/s | |
StringConnectBenchmark.testStringBuffer | thrpt | 30 | 112423521.098 ± 1157072.848 | ops/s | |
StringConnectBenchmark.testStringBuilder | thrpt | 30 | 110558976.274 ± 654163.111 | ops/s | |
StringConnectBenchmark.testStringConcat | thrpt | 30 | 44820009.200 ± 524305.660 | ops/s | |
StringConnectBenchmark.testStringFormat | thrpt | 30 | 1132633.183 ± 16252.303 | ops/s |
結論:
StringBuffer >= StringBuilder > String直接相加 > StringConcat >> StringFormat
可見 StringBuffer 與 StringBuilder 大體性能相同,都比直接相加高几個數量級,並且直接相加與 Concat 方法相加差很少。可是這裏無論哪一種都比 StringFormat高 N 個數量級。因此 String的 Format方法必定要慎用、不用、禁用!!!<br /> <a name="rsglln"></a>
<a name="lbmibh"></a>
JMH 學習筆記 ← 很不錯
<a name="bvnqgz"></a>
關注公衆號「KIWI的碎碎念」,分享的不只僅是技術
原文出處:https://www.cnblogs.com/kiwifly/p/11477153.html