概述
JMH只適合細粒度的方法測試,並不適用於系統之間的鏈路測試
使用Maven搭建基准測試項目骨架
JMH官方推薦使用Maven來搭建基准測試的骨架,使用也很簡單,使用如下命令來生成maven項目:
mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=org.sample -DartifactId=jmh-benchmark -Dversion=1.0
上面的maven命令使用了jmh-java-benchmark-archetype來生成java語言的基准測試骨架,如果使用其他語言可以將這個參數對應替換,所有可選參數參考jmh,生成的項目groupId是org.sample,artifaceId是test,執行完之后會在當前目錄下生成一個test目錄,切換到test目錄下執行
mvn clean install
就會生成benchmarks.jar,再使用java -jar benchmarks.jar
就可以執行基准測試了。
JMH參數配置
如果你想直接在已有maven項目中集成JMH,那也很簡單,手動在POM文件中添加以下兩個依賴就行了,
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.26</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.26</version>
<scope>provided</scope>
</dependency>
從maven archetype插件生成的pom文件來看,這個工程使用了maven-shade-plugin來將所有的依賴打包到同一個jar包中,並在Manifest文件中配置了Main-Class屬性,這樣就能直接通過java -jar命令來執行了。
注解說明
@Benchmark
@Benchmark標簽是用來標記測試方法的,只有被這個注解標記的話,該方法才會參與基准測試,但是有一個基本的原則就是被@Benchmark標記的方法必須是public的。
@Warmup
@Warmup用來配置預熱的內容,可用於類或者方法上,越靠近執行方法的地方越准確。一般配置warmup的參數有這些:
- iterations:預熱的次數。
- time:每次預熱的時間。
- timeUnit:時間單位,默認是s。
- batchSize:批處理大小,每次操作調用幾次方法。(后面用到)
@Measurement
用來控制實際執行的內容,配置的選項本warmup一樣。
@BenchmarkMode
@BenchmarkMode主要是表示測量的緯度,有以下這些緯度可供選擇:
- Mode.Throughput 吞吐量緯度
- Mode.AverageTime 平均時間
- Mode.SampleTime 抽樣檢測
- Mode.SingleShotTime 檢測一次調用
- Mode.All 運用所有的檢測模式 在方法級別指定@BenchmarkMode的時候可以一定指定多個緯度,例如: @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime}),代表同時在多個緯度對目標方法進行測量。
@OutputTimeUnit
@OutputTimeUnit代表測量的單位,比如秒級別,毫秒級別,微妙級別等等。一般都使用微妙和毫秒級別的稍微多一點。該注解可以用在方法級別和類級別,當用在類級別的時候會被更加精確的方法級別的注解覆蓋,原則就是離目標更近的注解更容易生效。
@State
在很多時候我們需要維護一些狀態內容,比如在多線程的時候我們會維護一個共享的狀態,這個狀態值可能會在每隔線程中都一樣,也有可能是每個線程都有自己的狀態,JMH為我們提供了狀態的支持。該注解只能用來標注在類上,因為類作為一個屬性的載體。 @State的狀態值主要有以下幾種:
- Scope.Benchmark 該狀態的意思是會在所有的Benchmark的工作線程中共享變量內容。
- Scope.Group 同一個Group的線程可以享有同樣的變量
- Scope.Thread 每隔線程都享有一份變量的副本,線程之間對於變量的修改不會相互影響。 下面看兩個常見的@State的寫法:
容器測試
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;
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;
/**
* @copyright: Copyright (c) 2020
* @company: 上海匯石數字科技有限公司
* @description:
* @author: Qi.Hong
* @create: 2020-10-13 12:15
*/
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
public class CollectionsTest {
private static final int TEN_MILLION = 10000000;
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void arrayList() {
List<String> array = new ArrayList<>();
for (int i = 0; i < TEN_MILLION; i++) {
array.add("123");
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void arrayListSize() {
List<String> array = new ArrayList<>(TEN_MILLION);
for (int i = 0; i < TEN_MILLION; i++) {
array.add("123");
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(CollectionsTest.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
輸出:
# JMH version: 1.26
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50693:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.sample.CollectionsTest.arrayList
# Run progress: 0.00% complete, ETA 00:00:20
# Fork: 1 of 1
# Warmup Iteration 1: 80599.837 us/op
# Warmup Iteration 2: 71816.276 us/op
# Warmup Iteration 3: 84441.683 us/op
# Warmup Iteration 4: 50829.796 us/op
# Warmup Iteration 5: 55140.199 us/op
Iteration 1: 47282.917 us/op
Iteration 2: 46334.255 us/op
Iteration 3: 46759.845 us/op
Iteration 4: 51672.301 us/op
Iteration 5: 47182.892 us/op
Result "org.sample.CollectionsTest.arrayList":
47846.442 ±(99.9%) 8361.853 us/op [Average]
(min, avg, max) = (46334.255, 47846.442, 51672.301), stdev = 2171.547
CI (99.9%): [39484.589, 56208.295] (assumes normal distribution)
# JMH version: 1.26
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50693:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.sample.CollectionsTest.arrayListSize
# Run progress: 50.00% complete, ETA 00:00:11
# Fork: 1 of 1
# Warmup Iteration 1: 27515.066 us/op
# Warmup Iteration 2: 24900.710 us/op
# Warmup Iteration 3: 25903.686 us/op
# Warmup Iteration 4: 25160.727 us/op
# Warmup Iteration 5: 24623.791 us/op
Iteration 1: 30331.259 us/op
Iteration 2: 34517.041 us/op
Iteration 3: 34419.362 us/op
Iteration 4: 34507.659 us/op
Iteration 5: 34385.104 us/op
Result "org.sample.CollectionsTest.arrayListSize":
33632.085 ±(99.9%) 7108.603 us/op [Average]
(min, avg, max) = (30331.259, 33632.085, 34517.041), stdev = 1846.082
CI (99.9%): [26523.482, 40740.688] (assumes normal distribution)
# Run complete. Total time: 00:00:22
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
CollectionsTest.arrayList avgt 5 47846.442 ± 8361.853 us/op
CollectionsTest.arrayListSize avgt 5 33632.085 ± 7108.603 us/op
Process finished with exit code 0