1. 為什么需要JMH
某些場(chǎng)景下需要精確地知道一段代碼的性能如何喉磁,如:
- 當(dāng)你已經(jīng)找出了熱點(diǎn)函數(shù)开缎,需要對(duì)熱點(diǎn)函數(shù)進(jìn)行進(jìn)一步優(yōu)化時(shí)粒褒;
- 想定量地知道某個(gè)函數(shù)需要執(zhí)行多長(zhǎng)時(shí)間,以及執(zhí)行時(shí)間和輸入 n 的相關(guān)性耿焊;
- 一個(gè)函數(shù)有兩種不同實(shí)現(xiàn)(例如JSON序列化/反序列化有Jackson和Gson實(shí)現(xiàn))揪惦,不知道哪種實(shí)現(xiàn)性能更好。
最簡(jiǎn)單的做法是在代碼執(zhí)行前后記錄下時(shí)間罗侯,然后計(jì)算一下時(shí)間差器腋,如:
long start = System.currentTimeMillis();
// 待測(cè)試的代碼塊...
System.out.println(System.currentTimeMillis() - start);
但是這樣做會(huì)有如下幾個(gè)問(wèn)題:
- System.currentTimeMillis() 如函數(shù)自身注釋所說(shuō),本身精度有限钩杰,根據(jù)操作系統(tǒng)不同纫塌,會(huì)存在數(shù)十毫秒左右的的誤差;
- JVM 在運(yùn)行時(shí)會(huì)進(jìn)行代碼預(yù)熱讲弄,說(shuō)白了就是越跑越快措左,因?yàn)轭?lèi)需要裝載、需要準(zhǔn)備操作垂睬;
- JVM 會(huì)在各個(gè)階段都有可能對(duì)代碼進(jìn)行優(yōu)化處理,比如某個(gè)計(jì)算的結(jié)果沒(méi)有被使用,那么這段代碼在執(zhí)行時(shí)就會(huì)被忽略驹饺,這樣的問(wèn)題比較難察覺(jué)钳枕;
- JVM垃圾回收的不確定性,可能運(yùn)行很快赏壹,回收很慢鱼炒;
- 使用不方便,配置不靈活蝌借,如果需要打印多種類(lèi)型的測(cè)試數(shù)據(jù)昔瞧,就需要增加很多額外的代碼,
不容易修改測(cè)試的類(lèi)型和條件菩佑。
因此自晰,使用一款靠譜的benchmark工具,既可以減少工作量稍坯,又可以確保性能優(yōu)化過(guò)程不被錯(cuò)誤的測(cè)試數(shù)據(jù)誤導(dǎo)酬荞。
Java Microbenchmark Harness(JMH) 是由 Java 虛擬機(jī)團(tuán)隊(duì)開(kāi)發(fā)的一款用于 Java的微基準(zhǔn)測(cè)試工具,微基準(zhǔn)是指方法(method)層面的測(cè)試基準(zhǔn)瞧哟,精度可以精確到微秒級(jí)混巧。使用JMH 可以讓你方便快速地進(jìn)行一次嚴(yán)格的代碼基準(zhǔn)測(cè)試,并且有多種測(cè)試模式勤揩,多種測(cè)試維度可供選擇咧党。
2. JMH使用
JMH的使用可以參考官方示例 Code Sample ,本文則會(huì)介紹 JMH 最典型的用法和部分常用選項(xiàng)陨亡。
2.1 引入JMH依賴(lài)
在maven的配置文件中增加如下依賴(lài)傍衡,最新的依賴(lài)版本可以參考:
https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core
https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.35</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.35</version>
</dependency>
2.2 代碼示例
這里以Java String替換為例進(jìn)行說(shuō)明,問(wèn)題來(lái)源于Commons Lang StringUtils.replace performance vs String.replace数苫,示例代碼來(lái)自于java-str-benchmark聪舒,本文稍加改動(dòng),最終代碼如附錄1所示虐急。
對(duì)比的替換方式有:
- String - JDK原生的字符串替換箱残;
- StringUtils - Commons Lang的StringUtils.replace;
- Lang3StringUtils - Commons Lang 3的StringUtils.replace止吁;
- OSGL - OSGL Java tool (2.0.0-SNAPSHOT)被辑;
- Fast - 自定義的快速字符串替換函數(shù)。
測(cè)試場(chǎng)景有:
- Short text - 將
AAAAAAAAAABBB
中的AA
替換成B
敬惦; - Short text no match -將
AAAAAAAAAABBB
中的XYZ
替換成B
盼理; - Long text - 將長(zhǎng)文本中的
occurrence
替換成appearance
; - Long text no match - 將長(zhǎng)文本中的
aaaxyz0001
替換成appearance
俄删。
2. 3. 執(zhí)行結(jié)果
measure item | String | StringUtils | Lang3StringUtils | OSGL | Fast |
---|---|---|---|---|---|
short text | 0.316 us/op | 0.107 us/op | 0.102 us/op | 0.164 us/op | 0.105 us/op |
long text | 14.492 us/op | 6.860 us/op | 11.324 us/op | 9.887 us/op | 7.005 us/op |
short text no match | 0.121 us/op | 0.010 us/op | 0.010 us/op | 0.008 us/op | 0.009 us/op |
long text no match | 3.008 us/op | 2.298 us/op | 2.319 us/op | 1.302 us/op | 3.359 us/op |
附錄
附錄1 StringReplaceBenchmark.java
package com.liuil.core.benchmark;
import org.apache.commons.lang.StringUtils;
import org.openjdk.jmh.annotations.*;
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;
import org.osgl.util.IO;
import org.osgl.util.S;
import java.util.concurrent.TimeUnit;
/**
* ref:
* https://stackoverflow.com/questions/16228992/commons-lang-stringutils-replace-performance-vs-string-replace
* https://github.com/greenlaw110/java-str-benchmark
*/
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Thread)
@Fork(1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
public class StringReplaceBenchmark {
public static final String TGT_NO_MATCH = "XYZ";
public static final String TGT = "AA";
public static final String REPLACEMENT = "B";
public static final String TEXT = "AAAAAAAAAABBB";
public static final String TGT_NO_MATCH_LONG = "aaaxyz0001";
public static final String TGT_LONG = "occurrence";
public static final String REP_LONG = "appearance";
public static final String TEXT_LONG = IO.readContentAsString(StringReplaceBenchmark.class.getResource("/long_str.txt"));
@State(Scope.Thread)
public static class BenchmarkState {
volatile private String str = TEXT;
volatile private String strLong = TEXT_LONG;
}
@Benchmark
public Object testString(BenchmarkState state) {
return state.str.replace(TGT, REPLACEMENT);
}
@Benchmark
public Object testStringUtils(BenchmarkState state) {
return StringUtils.replace(state.str, TGT, REPLACEMENT);
}
@Benchmark
public Object testLang3StringUtils(BenchmarkState state) {
return org.apache.commons.lang3.StringUtils.replace(state.str, TGT, REPLACEMENT);
}
@Benchmark
public Object testOsgl(BenchmarkState state) {
return S.have(state.str).replace(TGT).with(REPLACEMENT);
}
@Benchmark
public Object testFast(BenchmarkState state) {
return replace(state.str, TGT, REPLACEMENT);
}
@Benchmark
public Object testStringNoMatch(BenchmarkState state) {
return state.str.replace(TGT_NO_MATCH, REPLACEMENT);
}
@Benchmark
public Object testStringUtilsNoMatch(BenchmarkState state) {
return StringUtils.replace(state.str, TGT_NO_MATCH, REPLACEMENT);
}
@Benchmark
public Object testLang3StringUtilsNoMatch(BenchmarkState state) {
return org.apache.commons.lang3.StringUtils.replace(state.str, TGT_NO_MATCH, REPLACEMENT);
}
@Benchmark
public Object testOsglNoMatch(BenchmarkState state) {
return S.have(state.str).replace(TGT_NO_MATCH).with(REPLACEMENT);
}
@Benchmark
public Object testFastNoMatch(BenchmarkState state) {
return replace(state.str, TGT_NO_MATCH, REPLACEMENT);
}
@Benchmark
public Object testStringLong(BenchmarkState state) {
return state.strLong.replace(TGT_LONG, REP_LONG);
}
@Benchmark
public Object testStringUtilsLong(BenchmarkState state) {
return StringUtils.replace(state.strLong, TGT_LONG, REP_LONG);
}
@Benchmark
public Object testLang3StringUtilsLong(BenchmarkState state) {
return org.apache.commons.lang3.StringUtils.replace(state.strLong, TGT_LONG, REP_LONG);
}
@Benchmark
public Object testOsglLong(BenchmarkState state) {
return S.have(state.strLong).replace(TGT_LONG).with(REP_LONG);
}
@Benchmark
public Object testFastLong(BenchmarkState state) {
return replace(state.strLong, TGT_LONG, REP_LONG);
}
@Benchmark
public Object testStringLongNoMatch(BenchmarkState state) {
return state.strLong.replace(TGT_NO_MATCH_LONG, REPLACEMENT);
}
@Benchmark
public Object testStringUtilsLongNoMatch(BenchmarkState state) {
return StringUtils.replace(state.strLong, TGT_NO_MATCH_LONG, REPLACEMENT);
}
@Benchmark
public Object testLang3StringUtilsLongNoMatch(BenchmarkState state) {
return org.apache.commons.lang3.StringUtils.replace(state.strLong, TGT_NO_MATCH_LONG, REPLACEMENT);
}
@Benchmark
public Object testOsglLongNoMatch(BenchmarkState state) {
return S.have(state.strLong).replace(TGT_NO_MATCH_LONG).with(REPLACEMENT);
}
@Benchmark
public Object testFastLongNoMatch(BenchmarkState state) {
return replace(state.strLong, TGT_NO_MATCH_LONG, REPLACEMENT);
}
public static String replace(String source, String os, String ns) {
if (source == null) {
return null;
}
int i = 0;
if ((i = source.indexOf(os, i)) >= 0) {
char[] sourceArray = source.toCharArray();
char[] nsArray = ns.toCharArray();
int oLength = os.length();
StringBuilder buf = new StringBuilder(sourceArray.length);
buf.append(sourceArray, 0, i).append(nsArray);
i += oLength;
int j = i;
// Replace all remaining instances of oldString with newString.
while ((i = source.indexOf(os, i)) > 0) {
buf.append(sourceArray, j, i - j).append(nsArray);
i += oLength;
j = i;
}
buf.append(sourceArray, j, sourceArray.length - j);
source = buf.toString();
buf.setLength(0);
}
return source;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(StringReplaceBenchmark.class.getSimpleName()).build();
new Runner(opt).run();
}
}
附錄2 執(zhí)行結(jié)果
Benchmark Mode Cnt Score Error Units
StringReplaceBenchmark.testFast avgt 5 0.105 ± 0.012 us/op
StringReplaceBenchmark.testFastLong avgt 5 7.005 ± 0.170 us/op
StringReplaceBenchmark.testFastLongNoMatch avgt 5 3.359 ± 0.064 us/op
StringReplaceBenchmark.testFastNoMatch avgt 5 0.009 ± 0.001 us/op
StringReplaceBenchmark.testLang3StringUtils avgt 5 0.102 ± 0.002 us/op
StringReplaceBenchmark.testLang3StringUtilsLong avgt 5 11.324 ± 0.183 us/op
StringReplaceBenchmark.testLang3StringUtilsLongNoMatch avgt 5 2.319 ± 0.013 us/op
StringReplaceBenchmark.testLang3StringUtilsNoMatch avgt 5 0.010 ± 0.001 us/op
StringReplaceBenchmark.testOsgl avgt 5 0.164 ± 0.004 us/op
StringReplaceBenchmark.testOsglLong avgt 5 9.887 ± 0.340 us/op
StringReplaceBenchmark.testOsglLongNoMatch avgt 5 1.302 ± 0.031 us/op
StringReplaceBenchmark.testOsglNoMatch avgt 5 0.008 ± 0.002 us/op
StringReplaceBenchmark.testString avgt 5 0.316 ± 0.024 us/op
StringReplaceBenchmark.testStringLong avgt 5 14.492 ± 0.270 us/op
StringReplaceBenchmark.testStringLongNoMatch avgt 5 3.008 ± 0.050 us/op
StringReplaceBenchmark.testStringNoMatch avgt 5 0.121 ± 0.003 us/op
StringReplaceBenchmark.testStringUtils avgt 5 0.107 ± 0.002 us/op
StringReplaceBenchmark.testStringUtilsLong avgt 5 6.860 ± 0.290 us/op
StringReplaceBenchmark.testStringUtilsLongNoMatch avgt 5 2.298 ± 0.047 us/op
StringReplaceBenchmark.testStringUtilsNoMatch avgt 5 0.010 ± 0.001 us/op