什么是JMH
JMH是 Java Microbenchmark Harness 的縮寫。中文意思大致是 “JAVA 微基準測試套件”。
基準測試是指通過設計科學的測試方法、測試工具和測試系統,實現對一類測試對象的某項性能指標進行定量的和可對比的測試。——百度百科
為什么要使用 JMH
基準測試的特質有如下幾種:
- 可重復性:可進行重復性的測試,這樣做有利于比較每次的測試結果,得到性能結果的長期變化趨勢,為系統調優和上線前的容量規劃做參考。
- 可觀測性:通過全方位的監控(包括測試開始到結束,執行機、服務器、數據庫),及時了解和分析測試過程發生了什么。
- 可展示性:相關人員可以直觀明了的了解測試結果(web界面、儀表盤、折線圖樹狀圖等形式)。
- 真實性:測試的結果反映了客戶體驗到的真實的情況(真實準確的業務場景+與生產一致的配置+合理正確的測試方法)。
- 可執行性:相關人員可以快速的進行測試驗證修改調優(可定位可分析)。
可見要做一次符合特質的基準測試,是很繁瑣也很困難的。外界因素很容易影響到最終的測試結果。特別對于 JAVA的基準測試。
有些人認為Java是C++編寫的,一般來說Java編寫的程序不太可能比 C++編寫的代碼運行效率更好。但是Java在某些場景的確要比 C++運行的更高效。不要覺得天方夜譚。其實JVM隨著這些年的發展,已經變得很先進,它會在運行期間不斷的去優化。
這對于我們程序來說是好事,但是對于性能測試就頭疼的。你運行的次數與時間不同可能獲得的結果也不同,很難獲得一個比較穩定的結果。對于這種情況,有一個解決辦法就是大量的重復調用,并且在真正測試前還要進行一定的預熱,使結果盡可能的準確。
如何使用 JMH
導入依賴
新建一個Maven工程,導入依賴:
<!-- Java Microbenchmark Harness -->
<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>
基準測試代碼
package com.lun.string;import org.openjdk.jmh.annotations.Benchmark;public class StringConnectBenchmark {/*** 字符串拼接之 StringBuilder 基準測試*/@Benchmarkpublic void testStringBuilder() {print(new StringBuilder().append(1).append(2).append(3).toString());}/*** 字符串拼接之直接相加基準測試*/@Benchmarkpublic void testStringAdd() {print(new String()+ 1 + 2 + 3);}/*** 字符串拼接之String Concat基準測試*/@Benchmarkpublic void testStringConcat() {print(new String().concat("1").concat("2").concat("3"));}/*** 字符串拼接之 StringBuffer 基準測試*/@Benchmarkpublic void testStringBuffer() {print(new StringBuffer().append(1).append(2).append(3).toString());}/*** 字符串拼接之 StringFormat 基準測試*/@Benchmarkpublic void testStringFormat(){print(String.format("%s%s%s", 1, 2, 3));}public void print(String str) {}
}
運行基準測試代碼
package com.lun.string;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;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();}
}
運行結果
...
# Run progress: 93.33% complete, ETA 00:00:17
# Fork: 3 of 3
# Warmup Iteration 1: 168644.227 ops/s
# Warmup Iteration 2: 406543.452 ops/s
# Warmup Iteration 3: 417524.283 ops/s
# Warmup Iteration 4: 453008.474 ops/s
# Warmup Iteration 5: 450343.882 ops/s
Iteration 1: 452407.964 ops/s
Iteration 2: 454286.433 ops/s
Iteration 3: 446131.016 ops/s
Iteration 4: 449042.238 ops/s
Iteration 5: 457997.034 ops/s
Iteration 6: 450785.925 ops/s
Iteration 7: 450866.167 ops/s
Iteration 8: 439266.876 ops/s
Iteration 9: 453702.384 ops/s
Iteration 10: 451028.848 ops/sResult "com.lun.string.StringConnectBenchmark.testStringFormat":441747.293 ±(99.9%) 15980.806 ops/s [Average](min, avg, max) = (342074.817, 441747.293, 463951.126), stdev = 23919.319CI (99.9%): [425766.487, 457728.099] (assumes normal distribution)# Run complete. Total time: 00:04:23Benchmark Mode Cnt Score Error Units
StringConnectBenchmark.testStringAdd thrpt 30 11809268.588 ± 766758.418 ops/s
StringConnectBenchmark.testStringBuffer thrpt 30 68501244.128 ± 1373528.962 ops/s
StringConnectBenchmark.testStringBuilder thrpt 30 49564682.559 ± 7821622.523 ops/s
StringConnectBenchmark.testStringConcat thrpt 30 14246208.232 ± 443906.557 ops/s
StringConnectBenchmark.testStringFormat thrpt 30 441747.293 ± 15980.806 ops/s
Score分數越高越好
參考資料
- 【基準測試】JMH 簡單入門
- Java Micro Benchmark with JMH