java中的基准测试框架JMH
JHM是openJDK开发的一个benchmark框架。它是一个Maven依赖,所以创建一个Maven项目,引入下面两个依赖:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
</dependency>
两个依赖版本一样,版本号你可以去中心仓库查一下最新的,目前是1.37
然后就可以写一个测试类,比如我想对比一下ArrayList
和LinkedList
哪个性能好:
你猜哪个性能好?你可能会说给中间插入值链表更快,但是怎么知道插入到哪里呢...
基准配置
我的测试类叫MyBenchmark
,在main
方法中配置如下:
public static void main(String[] args) throws RunnerException {
System.out.println("Hello World!");
Options options = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
// 这里需要include测试类进来,使用simpleName就行
.forks(1)
// forks是启动几个JVM实例,每个实例单独测试,配置1就行
.timeout(TimeValue.seconds(10))
// timeout指定下面配置的每一次测试的超时时间,你可以改大一些
.threads(1)
// 线程数是同时用几个线程跑测试代码,比如我的笔记本是6核12线程的,3线程反而最好
.warmupIterations(1)
// 正式测试前跑几次热身。和正式测试流程一样,只是数据不计入统计结果
.warmupTime(TimeValue.seconds(10))
// 预热多久
.measurementTime(TimeValue.seconds(2))
// 正式测试每次多久
.measurementIterations(5)
// 测试几轮
.build();
new Runner(options).run();
}
Round One
第一轮测试代码如下:
public class MyBenchmark {
private static final int limit = 10_0000;
@Benchmark
public void testArrayList() {
List<Integer> list = new ArrayList<>(1);
for (int i = 0; i < limit; i++) {
list.add(i);
}
if (false) {
System.out.println("more");
}
}
@Benchmark
public void testLinkedList() {
List<Integer> list = new LinkedList<>();
for (int i = 0; i < limit; i++) {
list.add(i);
}
if (false) {
System.out.println("more");
}
}
}
给每个集合中加入10万个数,看每秒能执行多少次。你先猜一下结果。
执行main
方法,会打印出配置信息,比如
# JMH version: 1.37
# VM version: JDK 21.0.1, Java HotSpot(TM) 64-Bit Server VM, 21.0.1+12-jvmci-23.1-b19
# VM invoker: /.sdkman/candidates/java/21.0.1-graal/bin/java
# VM options: -XX:ThreadPriorityPolicy=1 -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCIProduct -XX:-UnlockExperimentalVMOptions -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53238:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 1 iterations, 10 s each
# Measurement: 5 iterations, 2 s each
# Timeout: 10 s per iteration, ***WARNING: The timeout might be too low!***
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
测试结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayList thrpt 5 1286.969 ± 1686.876 ops/s
MyBenchmark.testLinkedList thrpt 5 1909.538 ± 373.143 ops/s
数组每秒1000多次(每次插入10万数据),链表每秒快2000次了。链表取胜!
如果数据改成1000个,结果是
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayList thrpt 5 174194.500 ± 19431.634 ops/s
MyBenchmark.testLinkedList thrpt 5 169880.651 ± 74638.041 ops/s
数组险胜。
Round Two
这次把其中的操作改成排序,每插入一个数就进行一个倒序排序:
list.add(i);
list.sort(Comparator.reverseOrder());// 使用逆序,让他一定要移动数据
你猜结果如何?
先看1万个数据(10万的话我电脑可能完不成):
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayList thrpt 5 4.867 ± 2.272 ops/s
MyBenchmark.testLinkedList thrpt 5 0.638 ± 1.323 ops/s
数组完胜!
再看100个数据:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayList thrpt 5 79449.678 ± 97210.639 ops/s
MyBenchmark.testLinkedList thrpt 5 32889.346 ± 2018.265 ops/s
数组还是比链表优势大很多。
Round Three
这次往中间插入值:
list.add(i/2, 1);
// list.sort(Comparator.reverseOrder());
看10万数据的结果就行了,你猜结果啥样:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayList thrpt 5 3.606 ± 2.153 ops/s
MyBenchmark.testLinkedList thrpt 5 0.151 ± 0.112 ops/s