來源:https://juejin.cn/post/6844903936869007368
前言
"If you cannot measure it, you cannot improve it".
在日常開發中,我們對一些代碼的呼叫或者工具的使用會存在多種選擇方式,在不確定他們性能的時候,我們首先想要做的就是去測量它,大多數時候,我們會簡單的采用多次計數的方式來測量,來看這個方法的總耗時,
但是,如果熟悉JVM類加載機制的話,應該知道JVM默認的執行模式是JIT編譯與解釋混合執行,JVM通過熱點代碼統計分析,識別高頻方法的呼叫、回圈體、公共模塊等,基于JIT動態編譯技術,會將熱點代碼轉換成機器碼,直接交給CPU執行,

也就是說,JVM會不斷的進行編譯優化,這就使得很難確定重復多少次才能得到一個穩定的測驗結果?所以,很多有經驗的同學會在測驗代碼前寫一段預熱的邏輯,
JMH,全稱 Java Microbenchmark Harness (微基準測驗框架),是專門用于Java代碼微基準測驗的一套測驗工具API,是由 OpenJDK/Oracle 官方發布的工具,何謂 Micro Benchmark 呢?簡單地說就是在 method 層面上的 benchmark,精度可以精確到微秒級,
Java的基準測驗需要注意的幾個點:
- 測驗前需要預熱,
- 防止無用代碼進入測驗方法中,
- 并發測驗,
- 測驗結果呈現,
JMH的使用場景:
- 定量分析某個熱點函式的優化效果
- 想定量地知道某個函式需要執行多長時間,以及執行時間和輸入變數的相關性
- 對比一個函式的多種實作方式
本篇主要是介紹JMH的DEMO演示,和常用的注解引數,希望能對你起到幫助,
DEMO 演示
這里先演示一個DEMO,讓不了解JMH的同學能夠快速掌握這個工具的大概用法,
1. 測驗專案構建
JMH是內置Java9及之后的版本,這里是以Java8進行說明,
為了方便,這里直接介紹使用maven構建JMH測驗專案的方式,
第一種是使用命令列構建,在指定目錄下執行以下命令:
$ mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test \
-Dversion=1.0
復制代碼
對應目錄下會出現一個test專案,打開專案后我們會看到這樣的專案結構,

第二種方式就是直接在現有的maven專案中添加jmh-core和jmh-generator-annprocess的依賴來集成JMH,
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
2. 撰寫性能測驗
這里我以測驗LinkedList 通過index 方式迭代和foreach 方式迭代的性能差距為例子,撰寫測驗類,涉及到的注解在之后會講解,
/**
* @author Richard_yyf
* @version 1.0 2019/8/27
*/
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {
private static final int SIZE = 10000;
private List<String> list = new LinkedList<>();
@Setup
public void setUp() {
for (int i = 0; i < SIZE; i++) {
list.add(String.valueOf(i));
}
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
public void forIndexIterate() {
for (int i = 0; i < list.size(); i++) {
list.get(i);
System.out.print("");
}
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
public void forEachIterate() {
for (String s : list) {
System.out.print("");
}
}
}
3. 執行測驗
運行 JMH 基準測驗有兩種方式,一個是生產jar檔案運行,另一個是直接寫main函式或者放在單元測驗中執行,
生成jar檔案的形式主要是針對一些比較大的測驗,可能對機器性能或者真實環境模擬有一些需求,需要將測驗方法寫好了放在linux環境執行,具體命令如下
$ mvn clean install
$ java -jar target/benchmarks.jar
我們日常中遇到的一般是一些小測驗,比如我上面寫的例子,直接在IDE中跑就好了,啟動方式如下:
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(LinkedListIterationBenchMark.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(2)
.output("E:/Benchmark.log")
.build();
new Runner(opt).run();
}
4. 報告結果
輸出結果如下,
最后的結果:
Benchmark Mode Cnt Score Error Units
LinkedListIterationBenchMark.forEachIterate thrpt 2 1192.380 ops/s
LinkedListIterationBenchMark.forIndexIterate thrpt 2 206.866 ops/s
整個程序:
# Detecting actual CPU count: 12 detected
# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forEachIterate
# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 1
# Warmup Iteration 1: 1189.267 ops/s
# Warmup Iteration 2: 1197.321 ops/s
Iteration 1: 1193.062 ops/s
Iteration 2: 1191.698 ops/s
Result "org.sample.jmh.LinkedListIterationBenchMark.forEachIterate":
1192.380 ops/s
# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate
# Run progress: 50.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration 1: 205.676 ops/s
# Warmup Iteration 2: 206.512 ops/s
Iteration 1: 206.542 ops/s
Iteration 2: 207.189 ops/s
Result "org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate":
206.866 ops/s
# Run complete. Total time: 00:01:21
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark Mode Cnt Score Error Units
LinkedListIterationBenchMark.forEachIterate thrpt 2 1192.380 ops/s
LinkedListIterationBenchMark.forIndexIterate thrpt 2 206.866 ops/s
注解介紹
下面我們來詳細介紹一下相關的注解,
@BenchmarkMode
微基準測驗型別,JMH 提供了以下幾種型別進行支持:
| 型別 | 描述 |
|---|---|
| Throughput | 每段時間執行的次數,一般是秒 |
| AverageTime | 平均時間,每次操作的平均耗時 |
| SampleTime | 在測驗中,隨機進行采樣執行的時間 |
| SingleShotTime | 在每次執行中計算耗時 |
| All | 所有模式 |
可以注釋在方法級別,也可以注釋在類級別,
@BenchmarkMode(Mode.All)
public class LinkedListIterationBenchMark {
...
}
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
public void m() {
...
}
@Warmup
這個單詞的意思就是預熱,iterations = 3就是指預熱輪數,
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Warmup(iterations = 3)
public void m() {
...
}
@Measurement
正式度量計算的輪數,
iterations進行測驗的輪次time每輪進行的時長timeUnit時長單位
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Measurement(iterations = 3)
public void m() {
...
}
@Threads
每個行程中的測驗執行緒,
@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {
...
}
@Fork
進行 fork 的次數,如果 fork 數是3的話,則 JMH 會 fork 出3個行程來進行測驗,
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Fork(value = https://www.cnblogs.com/javastack/p/3)
public void m() {
...
}
@OutputTimeUnit
基準測驗結果的時間型別,一般選擇秒、毫秒、微秒,
@OutputTimeUnit(TimeUnit.SECONDS)
public class LinkedListIterationBenchMark {
...
}
@Benchmark
方法級注解,表示該方法是需要進行 benchmark 的物件,用法和 JUnit 的 @Test 類似,
@Param
屬性級注解,@Param 可以用來指定某項引數的多種情況,特別適合用來測驗一個函式在不同的引數輸入的情況下的性能,
@Setup
方法級注解,這個注解的作用就是我們需要在測驗之前進行一些準備作業,比如對一些資料的初始化之類的,
@TearDown
方法級注解,這個注解的作用就是我們需要在測驗之后進行一些結束作業,比如關閉執行緒池,資料庫連接等的,主要用于資源的回收等,
@State
當使用@Setup引數的時候,必須在類上加這個引數,不然會提示無法運行,
就比如我上面的例子中,就必須設定state,
State 用于宣告某個類是一個“狀態”,然后接受一個 Scope 引數用來表示該狀態的共享范圍,因為很多 benchmark 會需要一些表示狀態的類,JMH 允許你把這些類以依賴注入的方式注入到 benchmark 函式里,Scope 主要分為三種,
- Thread: 該狀態為每個執行緒獨享,
- Group: 該狀態為同一個組里面所有執行緒共享,
- Benchmark: 該狀態在所有執行緒間共享,
啟動方法
在啟動方法中,可以直接指定上述說到的一些引數,并且能將測驗結果輸出到指定檔案中,
/**
* 僅限于IDE中運行
* 命令列模式 則是 build 然后 java -jar 啟動
*
* 1. 這是benchmark 啟動的入口
* 2. 這里同時還完成了JMH測驗的一些配置作業
* 3. 默認場景下,JMH會去找尋標注了@Benchmark的方法,可以通過include和exclude兩個方法來完成包含以及排除的語意
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
// 包含語意
// 可以用方法名,也可以用XXX.class.getSimpleName()
.include("Helloworld")
// 排除語意
.exclude("Pref")
// 預熱10輪
.warmupIterations(10)
// 代表正式計量測驗做10輪,
// 而每次都是先執行完預熱再執行正式計量,
// 內容都是呼叫標注了@Benchmark的代碼,
.measurementIterations(10)
// forks(3)指的是做3輪測驗,
// 因為一次測驗無法有效的代表結果,
// 所以通過3輪測驗較為全面的測驗,
// 而每一輪都是先預熱,再正式計量,
.forks(3)
.output("E:/Benchmark.log")
.build();
new Runner(opt).run();
}
結語
基于JMH可以對很多工具和框架進行測驗,比如日志框架性能對比、BeanCopy性能對比 等,更多的example可以參考官方給出的JMH samples(https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/)
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.20w 程式員紅包封面,快快領取,,,
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/423717.html
標籤:Java
上一篇:Go標準的目錄結構(自總結)
下一篇:Dubbo原理決議(非常透徹)
