使用JMH做基准测试
JMH
的全称是 Java Microbenchmark Harness
,是一个 open JDK 中用来做性能测试的套件。
快速入门
添加依赖
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.19</version>
</dependency>
添加测试代码
简单写一个测试代码,测试一下 StringBuilder
和 String
做字符串拼接的性能差异。
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StringBuilderBenchmark {
@Benchmark
public void string() {
String a = "";
for (int i = 0; i < 10; i++) {
a += i;
}
}
@Benchmark
public void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(StringBuilderBenchmark.class.getSimpleName())
.build();
new Runner(options).run();
}
}
运行代码
执行 main
方法,运行完成之后,会生成一个测试报告。
Result "com.lyennon.StringBuilderBenchmark.stringBuilder":
72545.543 ±(99.9%) 3588.622 ops/ms [Average]
(min, avg, max) = (63846.713, 72545.543, 80526.887), stdev = 4132.663
CI (99.9%): [68956.921, 76134.166] (assumes normal distribution)
# Run complete. Total time: 00:03:35
Benchmark Mode Cnt Score Error Units
StringBuilderBenchmark.string thrpt 20 20298.874 ± 1278.400 ops/ms
StringBuilderBenchmark.stringBuilder thrpt 20 72545.543 ± 3588.622 ops/ms
JMH API
@BenchmarkMode
用于声明基准测试运行时候默认的模式,JMH
支持的基准测试类型包括:
Throughput
- 整体吞吐量。类似于QPS
,例如 “1秒内执行了多少次”。AverageTime
- 平均耗时。例如 “执行1次, 用了多少秒”。SampleTime
- 直方图统计。随机采样耗时,最后会得到采样结果的直方图.,例如 “执行的p995, p99 耗时是多少”。SingleShotTime
- 运行一次的时间。可以用于测试冷启动时的性能。All
- 所有模式。
@Warmup
运行基准测试之前,首先要进行预热,以保证测试结果的准确性。
为什么需要预热?程序在冷启动之后,执行速度一般会比较慢,运行一段时间预热之后,执行速度会有一些提高,测试结果会更准确。
iterations
- 预热的轮次。time
- 每轮进行的时长。timeUnit
- 时长的单位。batchSize
- 每次执行调用测试代码的次数。
@Measurement
运行基准测试的一些参数,和 @Warmup
的参数类似。
@Threads
每个进程中的测试线程数,根据具体情况选择,一般为 cpu 乘以 2。
@Fork
运行基准测试的进程数。
@OutputTimeUnit
输出的测试报告的时间单位。
@Benchmark
标注在方法上,表示这是一个需要做性能测试的方法。
@Param
标注在属性上,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。例如
@Param({"1", "31", "65", "101", "103"})
public int arg;
@Param({"0", "1", "2", "4", "8", "16", "32"})
public int certainty;
@Benchmark
public boolean bench() {
return BigInteger.valueOf(arg).isProbablePrime(certainty);
}
arg
的参数有5种情况,certainty
的参数有7中情况,加起来总共有 5*7=35
中情况。
@Setup
标注在方法上,可以用来在执行测试之前做一些准备工作。@Setup
只能标注在 @State
标注的 class 里面的方法上。
@TearDown
标注在方法上,可以用来在执行测试之后进行一些结束工作,比如关闭线程池、数据库连接等的,主要用于资源的回收等。
@State
标注在类上,用来表示这个类是一个状态类,可以通过 Scope
参数用来表示该状态的共享范围。Scope
有三种:
Benchmark
- 所有线程间共享。Group
- 同一个组里面所有线程共享。Thread
- 每个线程独享。
参考资料
JMH
官方示例代码: [https://github.com/openjdk/jmh/tree/master/jmh-samples/src/main/java/org/openjdk/jmh/samples](