JMH簡介
官網(wǎng):http://openjdk.java.net/projects/code-tools/jmh/
簡介:JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM鸵赫,由簡介可知掩浙,JMH不止能對Java語言做基準(zhǔn)測試谣妻,還能對運(yùn)行在JVM上的其他語言做基準(zhǔn)測試鳞滨。而且可以分析到納秒級別累盗。
推薦用法
官方推薦創(chuàng)建一個獨立的Maven工程來運(yùn)行JMH基準(zhǔn)測試寒矿,這樣更能確保結(jié)果的準(zhǔn)確性。當(dāng)然也可以在已存在的工程中若债,或者在IDE上運(yùn)行符相,但是越復(fù)雜,結(jié)果越不可靠(more complex and the results are less reliable)蠢琳。
簡單實用
推薦用法通過命令行創(chuàng)建啊终,構(gòu)建和運(yùn)行JMH基準(zhǔn)測試。
setup
生成一個新的JMH工程的maven命令如下:
mvn archetype:generate
-DinteractiveMode=false
-DarchetypeGroupId=org.openjdk.jmh
-DarchetypeArtifactId=jmh-java-benchmark-archetype
-DgroupId=com.afei.jmh
-DartifactId=jmh
-Dversion=1.0.0-SNAPSHOT
執(zhí)行該命令后傲须,會創(chuàng)建一個Maven工程蓝牲,但是默認(rèn)生成的MyBenchmark.java并沒有在預(yù)期的包名com/afei/jmh中,即使加上參數(shù)-DpackageName=com.afei.jmh
也不行泰讽,只能先手動將其挪到包名下例衍,這里作為一個小小的遺留問題。
壓測代碼
默認(rèn)生成的MyBenchmark.java源碼如下已卸,testMethod()中就是你要壓測的代碼佛玄,下面是筆者要壓測的洗牌算法:
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
@GenerateMicroBenchmark
public List<Integer> testMethod() {
int cardCount = 54;
List<Integer> cardList = new ArrayList<Integer>();
for (int i=0; i<cardCount; i++){
cardList.add(i);
}
// 洗牌算法
Random random = new Random();
for (int i=0; i<cardCount; i++) {
int rand = random.nextInt(cardCount);
Collections.swap(cardList, i, rand);
}
return cardList;
}
}
build
寫完代碼接下來就是構(gòu)建并打包,在pom.xml所在目錄執(zhí)行如下命令:
mvn clean package
說明:這一步咬最,也可以通過IDE工具構(gòu)建打包。
running
打包成功后在target目錄下生成了一個JAR文件:microbenchmarks.jar
欠动,需要注意的是永乌,官網(wǎng)的運(yùn)行命令是java -jar target/benchmarks.jar
,至于到底是benchmarks.jar還是microbenchmarks.jar具伍,取決于你的POM文件:
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
筆者生成的maven工程是microbenchmarks翅雏,所以,運(yùn)行時執(zhí)行如下命令:
java -jar target/microbenchmarks.jar
輸出結(jié)果如下:
# Run progress: 0.00% complete, ETA 00:00:10
# VM invoker: C:\Program Files\Java\jre1.8.0_181\bin\java.exe
# VM options: <none>
# Fork: 1 of 1
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.afei.jmh.MyBenchmark.testMethod
# Warmup Iteration 1: 1133.738 ns/op
# Warmup Iteration 2: 1169.750 ns/op
# Warmup Iteration 3: 1066.204 ns/op
# Warmup Iteration 4: 1086.300 ns/op
# Warmup Iteration 5: 1145.228 ns/op
Iteration 1: 1045.157 ns/op
Iteration 2: 1064.303 ns/op
Iteration 3: 1064.227 ns/op
Iteration 4: 1053.979 ns/op
Iteration 5: 1055.718 ns/op
Result : 1056.677 ±(99.9%) 30.809 ns/op
Statistics: (min, avg, max) = (1045.157, 1056.677, 1064.303), stdev = 8.001
Confidence interval (99.9%): [1025.868, 1087.486]
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod avgt 5 1056.677 30.809 ns/op
結(jié)果解讀
下面對輸出結(jié)果一些重要信息進(jìn)行解讀:
@Warmup
由于筆者加了這個注解:@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
人芽。所以望几,基準(zhǔn)測試后對代碼預(yù)熱總計5秒(迭代5次,每次1秒)萤厅。預(yù)熱對于壓測來說非常非常重要橄抹,如果沒有預(yù)熱過程靴迫,壓測結(jié)果會很不準(zhǔn)確。這個注解對應(yīng)的日志如下:
# Warmup Iteration 1: 1133.738 ns/op
# Warmup Iteration 2: 1169.750 ns/op
# Warmup Iteration 3: 1066.204 ns/op
# Warmup Iteration 4: 1086.300 ns/op
# Warmup Iteration 5: 1145.228 ns/op
@Measurement
另外一個重要的注解:@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
楼誓,表示循環(huán)運(yùn)行5次玉锌,總計5秒時間。
@Fork
這個注解表示fork多少個線程運(yùn)行基準(zhǔn)測試疟羹,如果@Fork(1)
主守,那么就是一個線程,這時候就是同步模式榄融。
@BenchmarkMode&@OutputTimeUnit
基準(zhǔn)測試模式申明為:@BenchmarkMode(Mode.AverageTime)
搭配@OutputTimeUnit(TimeUnit.NANOSECONDS)
(可選基準(zhǔn)測試模式通過枚舉Mode得到)参淫,筆者的示例是AverageTime,即表示每次操作需要的平均時間愧杯,而OutputTimeUnit申明為納秒涎才,所以基準(zhǔn)測試單位是ns/op,即每次操作的納秒單位平均時間民效°疚基準(zhǔn)測試結(jié)果如下:
Result : 1056.677 ±(99.9%) 30.809 ns/op
Statistics: (min, avg, max) = (1045.157, 1056.677, 1064.303), stdev = 8.001
Confidence interval (99.9%): [1025.868, 1087.486]
最后一段結(jié)果如下,重點關(guān)注Mean和Units兩個字段畏邢,組合起來就是1227.928ns/op业扒,即每次操作耗時1056.677納秒:
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod avgt 5 1056.677 30.809 ns/op
如果我們將@BenchmarkMode(Mode.AverageTime)
與@OutputTimeUnit(TimeUnit.NANOSECONDS)
的組合,改成@BenchmarkMode(Mode.Throughput)
和@OutputTimeUnit(TimeUnit.MILLISECONDS)
舒萎,那么基準(zhǔn)測試結(jié)果就是每毫秒的吞吐量(即每毫秒多少次操作)程储,結(jié)果如下,表示943.437ops/ms:
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod thrpt 5 943.437 44.060 ops/ms
Mean error
表示誤差臂寝,或者波動章鲤,與Result的±值對應(yīng):Result : 1056.677 ±(99.9%) 30.809 ns/op
;
基準(zhǔn)測試對比
將自定義洗牌算法和JDK原生的洗牌算法Collections.shuffle(cardList);
進(jìn)行基準(zhǔn)測試對比咆贬,結(jié)果如下:
- | ops/ms | ns/op |
---|---|---|
JDK原生洗牌算法 | 807.470 | 1149.900 |
自定義洗牌算法(for循環(huán)外面new Random) | 943.437 | 1056.677 |
自定義洗牌算法(for循環(huán)里面new Random) | 300.467 | 3346.509 |
Random在for循環(huán)里面的源碼如下:
for (int i=0; i<cardCount; i++) {
Random random = new Random();
int rand = random.nextInt(cardCount);
Collections.swap(cardList, i, rand);
}
說明败徊,自定義洗牌算法事實上就是JDK自帶洗牌算法中集合的size少于SHUFFLE_THRESHOLD(這個值為5)時的實現(xiàn)。另外掏缎,由基準(zhǔn)測試對比可知皱蹦,for循環(huán)里面不斷new Random的性能相比只在for循環(huán)外面new Random一次的性能要差好3倍左右。
另外眷蜈,在集合的size超過SHUFFLE_THRESHOLD即5后JDK原生洗牌算法沪哺,相比size少于該值得洗牌算法性能并沒有提高,兩者性能差在10%左右酌儒,基本可以忽略辜妓。這里想不明白,JDK原生洗牌算法在集合的size超過SHUFFLE_THRESHOLD的優(yōu)化的意思,當(dāng)然也可能跟筆者基準(zhǔn)測試樣本有關(guān)(畢竟筆者只測試了size為54的集合)籍滴。
JMH和jMeter的不同
JMH和jMeter的使用場景還是有很大的不同的酪夷,jMeter更多的是對rest api進(jìn)行壓測,而JMH關(guān)注的粒度更細(xì)异逐,它更多的是發(fā)現(xiàn)某塊性能槽點代碼捶索,然后對優(yōu)化方案進(jìn)行基準(zhǔn)測試對比。比如json序列化方案對比灰瞻,bean copy方案對比腥例,文中提高的洗牌算法對比等。
案例參考
官方給了很多樣例代碼酝润,有興趣的同學(xué)可以自己查詢并學(xué)習(xí)JMH:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/