【资源】
- Github:github.com/google/cali…
- 官网Sample:hg.openjdk.java.net/code-tools/…
1. JMH介绍
JMH主要是为了测量那些体量比较“小”的代码,通常运行的时间为次毫秒级别,代码一般要跟I/O开销无关。如:
- java的final字段会不会影响性能?详细请看
- System.arraycopy() vs Arrays.copyOf(),详细请看
- HikariCP Connection pool性能对比,详细请看
2. 举个例子
我们都知道String连接有三种方式,那么我们就写一个测试,来看看这三种方式的效率:
-
String直接用+连接。
-
StringBuffer的append方法,线程安全。
-
StringBuilder的append方法,线程不安全。
我们用JMH写下benchmark用例:
BenchmarkMode:JMH测试使用的模式,这里用的是Throughput:即整体吞吐量,例如“1秒内可以执行多少次调用”,单位是操作数/时间。Warmup:先预热3轮,相当于先空跑3轮,再开始benchmarkMeasurement:进行10轮测试,每轮5秒。
关于其它注解的解释,请看文章未尾的【参考】第二篇文章的链接,总结的非常好!!!
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StringPerformance {
@Benchmark
public String StringAdd() {
String result = "";
for (int i = 0; i < 10; i++) {
result += i;
}
return result;
}
@Benchmark
public StringBuilder StringBuilderAdd() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; i++) {
stringBuilder.append(i);
}
return stringBuilder;
}
@Benchmark
public StringBuffer StringBufferAdd() {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 10; i++) {
stringBuffer.append(i);
}
return stringBuffer;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(StringPerformance.class.getSimpleName())
.resultFormat(ResultFormatType.JSON)
.result("StringPerformance.json")
.build();
new Runner(options).run();
}
运行main方法后,生成的报告分四部分,前三部分是我上述三个@Benchmark的方法,最后一部分是汇总。
首先我贴出StringAdd的报告(另外两个方法的报告略):
可以看出每执行一个@Benchmark方法,都会先打印出这次报告的预置参数,如预热了3轮,每轮预热10s钟,测试需要10轮,每轮5s。fork为2,则 JMH 会 fork 出两个进程来进行测试。
# JMH version: 1.33
# VM version: JDK 17.0.4, Java HotSpot(TM) 64-Bit Server VM, 17.0.4+11-LTS-179
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-17.0.4.jdk/Contents/Home/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=64113:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect)
# Warmup: 3 iterations, 10 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.StringPerformance.StringAdd
# Run progress: 0.00% complete, ETA 00:08:00
# Fork: 1 of 2
# Warmup Iteration 1: 17858.914 ops/ms
# Warmup Iteration 2: 19230.153 ops/ms
# Warmup Iteration 3: 18751.680 ops/ms
Iteration 1: 18379.046 ops/ms
Iteration 2: 18460.512 ops/ms
Iteration 3: 18455.847 ops/ms
Iteration 4: 18268.708 ops/ms
Iteration 5: 18325.388 ops/ms
Iteration 6: 18213.200 ops/ms
Iteration 7: 18124.646 ops/ms
Iteration 8: 18689.763 ops/ms
Iteration 9: 18570.386 ops/ms
Iteration 10: 18655.092 ops/ms
# Run progress: 16.67% complete, ETA 00:06:46
# Fork: 2 of 2
# Warmup Iteration 1: 19125.319 ops/ms
# Warmup Iteration 2: 18887.678 ops/ms
# Warmup Iteration 3: 19061.692 ops/ms
Iteration 1: 19108.268 ops/ms
Iteration 2: 19108.676 ops/ms
Iteration 3: 19173.356 ops/ms
Iteration 4: 18735.736 ops/ms
Iteration 5: 19025.315 ops/ms
Iteration 6: 19129.713 ops/ms
Iteration 7: 19143.510 ops/ms
Iteration 8: 19155.557 ops/ms
Iteration 9: 19184.028 ops/ms
Iteration 10: 18640.211 ops/ms
Result "com.StringPerformance.StringAdd":
18727.348 ±(99.9%) 321.835 ops/ms [Average]
(min, avg, max) = (18124.646, 18727.348, 19184.028), stdev = 370.626
CI (99.9%): [18405.513, 19049.183] (assumes normal distribution)
最后贴出总的报告,可以看出报告还是很符合预期的,即:
String直接相加是最慢的(每秒的执行次数最少)。StringBuffer是线程安全的,所以在append的时候需要加锁。- 而
StringBuilder是线程不安全的,在append的时候不会加锁,因此吞吐量最高,即Score遥遥领先!
Benchmark Mode Cnt Score Error Units
StringPerformance.StringAdd thrpt 20 18727.348 ± 321.835 ops/ms
StringPerformance.StringBufferAdd thrpt 20 23633.983 ± 294.191 ops/ms
StringPerformance.StringBuilderAdd thrpt 20 54591.306 ± 504.498 ops/ms
然后在项目根目录下,会生成StringPerformance.json文件。原因是我在main函数中加入了resultFormat方法为ResultFormatType.JSON,以及result方法,填的是文件路径+文件名,默认为根目录。
3. 可视化报告
有两个网站都可提供可视化图形报告,需要我们上传.json的文件。
第一个网站的界面
鼠标移到每个方法(蓝色条纹)上面,可以看到每轮的吞吐量:
第二个网站的界面
这个网站的github:deepoove.com/jmh-visual-…
4. 其它,IntelliJ idea插件:
参考:
- Microbenchmarking with Java
-->入门 - 使用JMH做Benchmark基准测试
-->详细!对于各个注解都有解释 - Java微基准测试框架JMH
- How to benchmark using jmh in Java