Java 微基准测试 JMH
Java JMH About 4,481 words添加依赖
<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>
<scope>provided</scope>
</dependency>
IDEA 插件
https://github.com/artyushov/idea-jmh-plugin
@Warmup
预热。可以用在类或者方法上。
预热过程的测试数据,是不记录测量结果的。
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
注解含义:对代码预热总计3
秒(迭代3
次,每次1
秒)
iterations
:预热阶段的迭代数。time
:每次预热的时间。timeUnit
:时间单位,默认的单位是秒。
@Measurement
真正的迭代次数。
Measurement
和Warmup
的参数是一样的。
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode
基准测试类型。可以用在类或者方法上。
注解中值是一个数组,可以配置多个统计维度,还可以设置为Mode.All
,即全部执行一遍。
Throughput
:整体吞吐量,每秒执行了多少次调用,单位时间内的调用量等,单位为ops/time
。AverageTime
:平均耗时,每次执行的平均时间。如果这个值很小不好辨认,可以把统计的单位时间调小一点,单位为time/op
。SampleTime
:随机取样,最后输出取样结果的分布。SingleShotTime
:只运行一次,往往同时把Warmup
次数设为0
,用于测试冷启动时的性能。其实和传统的main
方法没有什么区别。All
:上面的所有模式都执行一次。
@BenchmarkMode(Mode.Throughput)
// @BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@Fork
使用进程个数(注意不是线程)。可以用在类或者方法上。
一般设置成1
,表示只使用一个进程进行测试。
如果这个数字大于1
,表示会启用新的进程进行测试;但如果设置成0
,程序依然会运行,不过这样是在用户的JVM
进程上运行的,但不推荐这么做。
平常的测试中,也可以适当增加Fork
数,来减少测试的误差。
@Fork
注解有一个参数叫做jvmArgsAppend
,我们可以通过它传递一些JVM
的参数。
@Fork(value = 3, jvmArgsAppend = {"-Xmx2048m", "-server", "-XX:+AggressiveOpts"})
@Threads
Fork
是面向进程的,而Threads
是面向线程的。可以用在类或者方法上。
如果配置了Threads.MAX
,则使用和处理机器核数相同的线程数。
@Threads(2)
@Group
把测试方法进行归类。只能作用在方法上。
如果单个测试文件中方法比较多,或者需要将其归类,则可以使用这个注解。
@State
指定类中变量的作用范围。它有三个取值。
@State
可以被继承使用,如果父类定义了该注解,子类则无需定义。
Scope.Benchmark
:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能。Scope.Group
:联系上面的@Group
注解,同一个线程在同一个Group
里共享实例。Scope.Thread
:默认的State
,每个测试线程分配一个实例,互不影响。
@State(Scope.Thread)
@Param
指定某项参数的多种情况,只能作用在字段上。
使用该注解必须定义@State
注解。
特别适合用来测试一个函数在不同的参数输入的情况下的性能。
@Param(value = {"10", "50", "100"})
private int length;
JMH 陷阱
在使用JMH
的过程中,一定要避免一些陷阱。
比如JIT
优化中的死码消除,比如以下代码:
@Benchmark
public void testStringAdd() {
String a = "";
for (int i = 0; i < length; i++) {
a += i;
}
}
JVM
可能会认为变量a
从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。
JMH
提供了两种方式避免这种问题,一种是将这个变量作为方法返回值return a
,一种是通过Blackhole
的consume
来避免JIT
的优化消除。
其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用Fork
隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的可以阅读 https://github.com/lexburner/JMH-samples 了解全部的陷阱。
示例
@BenchmarkMode(Mode.Throughput)
//@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(2)
public class RandomBenchmark {
@Param(value = {"10", "50", "100"})
private int length;
@Benchmark
public void testRandom(Blackhole blackhole) {
double random = 0;
for (int i = 0; i < length; i++) {
random = Math.random();
}
blackhole.consume(random);
}
@Benchmark
public void testSecureRandom(Blackhole blackhole) {
for (int i = 0; i < length; i++) {
SecureRandom secRandom = new SecureRandom();
blackhole.consume(secRandom.nextInt());
}
}
public static void main(String[] args) throws Exception {
Options opts = new OptionsBuilder()
.include(RandomBenchmark.class.getSimpleName())
.resultFormat(ResultFormatType.JSON)
.build();
new Runner(opts).run();
}
}
测试结果
Error
是正负偏差
Benchmark (length) Mode Cnt Score Error Units
RandomBenchmark.testRandom 10 thrpt 5 1575.584 ± 286.636 ops/ms
RandomBenchmark.testRandom 50 thrpt 5 240.167 ± 102.101 ops/ms
RandomBenchmark.testRandom 100 thrpt 5 122.695 ± 46.519 ops/ms
RandomBenchmark.testSecureRandom 10 thrpt 5 48.633 ± 13.283 ops/ms
RandomBenchmark.testSecureRandom 50 thrpt 5 10.155 ± 0.621 ops/ms
RandomBenchmark.testSecureRandom 100 thrpt 5 4.986 ± 0.988 ops/ms
JMH 可视化
JMH
支持5
种格式的结果:
TEXT
:导出文本文件。JSON
导出成JSON
文件。CSV
:导出CSV
格式文件。SCSV
:导出SCSV
等格式的文件。LATEX
:导出到LATEX
,一种基于ΤΕΧ
的排版系统。
将测试结果以图表的形式可视化:
JMH Visual Chart
:http://deepoove.com/jmh-visual-chartJMH Visualizer
:https://jmh.morethan.io
开源地址
https://github.com/openjdk/jmh
参考
https://www.cnblogs.com/wupeixuan/archive/2020/06/11/13091381.html
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓