JMH简介
JMH是专门用于代码微基准测试的工具集。JMH是由实现Java虚拟机的团队开发的,由于现在JVM已经变得越来越智能,在Java文件的编译阶段、类的加载阶段,以及运行阶段可能进行了不同程度的优化,因此开发者编写的代码在运行中未必会像自己所预期的那样具有相同的性能体现。
JMH快速入门
最常见的就是ArrayList和LinkedList的性能比较,我们分别进行1000组测试,每组测试都会将List执行1000000次的add调用。
用main方法测试
public class ArrayListVsLinkedList {
private final static String DATA_STRING = "DUMMY DATA";
private final static int MAX_CAPACITY = 1_000_000;
private final static int MAX_ITERATIONS = 1000;
private static void test(List<String> list) {
for (int i = 0; i < MAX_CAPACITY; i++) {
list.add(DATA_STRING);
}
}
private static void arrayListPerfTest(int iterations) {
final List<String> list = new ArrayList<>();
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
test(list);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
private static void linkListPerfTest(int iterations) {
final List<String> list = new LinkedList<>();
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
test(list);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
public static void main(String[] args) {
arrayListPerfTest(MAX_ITERATIONS);
System.out.println(Strings.repeat('#',100));
linkListPerfTest(MAX_ITERATIONS);
}
}
乍一看ArrayList的add方法性能要好于LinkedList的add方法,事实上,ArrayLIst的随机写性能确实好于LinkedList(尤其是在ArrayList不进行内部数组扩容复制的条件下),LinkedList由于链表的设计,其delete操作的性能会好于ArrayList,我们这种测试会有几个问题:
- 使用StopWatch进行时间计算,其实是在StopWatch内部记录了方法执行开始纳秒数,这种操作本质会导致消耗CPU时间的浪费。
- 在代码的运行过程中,JVM可能会优化,比如循环展开、运行时编译等,这样会导致某组未经过优化的性能数据参与统计。
使用JMH做微基准测试
<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>
<scope>provided</scope>
</dependency>
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class JMHExample1 {
private final static String DATA_STATE = "DUMMY DATA";
private List<String> arrayList;
private List<String> linkedList;
@Setup(Level.Iteration)
public void setup() {
arrayList = new ArrayList<>();
linkedList = new LinkedList<>();
}
@Benchmark
public List<String> arrayListAdd() {
arrayList.add(DATA_STATE);
return arrayList;
}
@Benchmark
public List<String> linkedListAdd() {
linkedList.add(DATA_STATE);
return linkedList;
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder().include(JMHExample1.class.getSimpleName()).forks(1).measurementIterations(10).warmupIterations(10).build();
new Runner(opts).run();
}
}
运行main方法,可以得到以下输出:
得出的结论是ArrayList的add方法性能要好于LinkedList的性能使用JMH显然更加严谨。
@Benchmark标记基准测试方法
如果一个类没有一个方法被@Benchmark标记的话,那么对其进行基准测试会报错。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class JMHExample2 {
public void normalMethod() {
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder().include(JMHExample2.class.getSimpleName()).forks(1).measurementIterations(10).warmupIterations(10).build();
new Runner(opts).run();
}
}
结果如下:
@Warmup及@Measurement
这两个注解既可作用在类上,也可以作用在方法上,当类和方法上都有的时候,这个时候方法上的会覆盖掉类上面的,当Option中代码设置这两个字段话,优先级是最高的,注解就失效了。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Measurement(iterations = 5)
@Warmup(iterations = 2)
public class JMHExample3 {
@Benchmark
public void test() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@Measurement(iterations = 10)
@Warmup(iterations = 3)
@Benchmark
public void test2() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder()
.include(JMHExample3.class.getSimpleName())
.forks(1)
// .measurementIterations(10)
// .warmupIterations(10)
.build();
new Runner(opts).run();
}
}
这个时候可以看到的结果是方法上覆盖掉了类上面的。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Measurement(iterations = 5)
@Warmup(iterations = 2)
public class JMHExample3 {
@Benchmark
public void test() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@Measurement(iterations = 10)
@Warmup(iterations = 3)
@Benchmark
public void test2() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder()
.include(JMHExample3.class.getSimpleName())
.forks(1)
.measurementIterations(10)
.warmupIterations(10)
.build();
new Runner(opts).run();
}
}
这个时候可以看到options优先级最高,解释下相关的输出参数
四大BenchmarkMode
Throughput("thrpt", "Throughput, ops/time"),
AverageTime("avgt", "Average time, time/op"),
SampleTime("sample", "Sampling time"),
SingleShotTime("ss", "Single shot invocation time"),
All("all", "All benchmark modes");
分别有四种:
类型 | 解释 |
---|---|
Throughput | 吞吐量 |
AverageTime | 平均响应时间 |
SampleTime | 时间采样 |
SingleShotTime | 冷测试 |
all | 前面四种 |
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Measurement(iterations = 1)
@Warmup(iterations = 1)
public class JMHExample4 {
@BenchmarkMode(Mode.AverageTime)
@Benchmark
public void testAverageTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@BenchmarkMode(Mode.Throughput)
@Benchmark
public void testThroughput() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@BenchmarkMode(Mode.SampleTime)
@Benchmark
public void testSampleTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@BenchmarkMode(Mode.SingleShotTime)
@Benchmark
public void testSingleShotTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@BenchmarkMode({
Mode.AverageTime,Mode.Throughput})
@Benchmark
public void testAverageTimeAndThroughput() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
@BenchmarkMode(Mode.All)
@Benchmark
public void testAll() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(10);
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder()
.include(JMHExample4.class.getSimpleName())
.forks(1)
.build();
new Runner(opts).run();
}
}
三大State的使用
public enum Scope {
Benchmark
Group,
Thread,
}
类型 | 描述 |
---|---|
Benchmark线程共享 | |
Group | 线程组共享,比如多线程两个方法操作一个变量 |
Thread | 每个线程都会持有一个独立的对象 |
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 5)
@Warmup(iterations = 10)
@Threads(5)
@Fork(1)
public class JMHExample6 {
@State(Scope.Thread)
//每个线程都会创建,可以看到5次 create instance
public static class Test {
public Test() {
System.out.println("create instance");
}
public void method() {
}
}
@Benchmark
public void test(Test test) {
test.method();
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder()
.include(JMHExample6.class.getSimpleName())
.forks(1)
.build();
new Runner(opts).run();
}
}