Java中使用JMH(Java Microbenchmark Harness 微基准测试框架)进行性能测试和优化

场景

Jmeter进行http接口压力测试:

https://www.cnblogs.com/badaoliumangqizhi/p/16301432.html

JMH

JMH,全称Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,

是由Java虚拟机团队开发的的,一般用于代码的性能调优。

MicroBenchmark就是在method层面上的benchmark,精度可以精确到微秒级、甚至可以达到纳秒级别,

适用于 java 以及其他基于 JVM 的语言。与Apache JMeter 不同,JMH 测试的对象可以是任一方法,颗粒度更小,

而不仅限于接口以及API层面。

JMH比较典型的应用场景

想要知道某个函数需要执行多长时间,以及执行时间和输入之间的相关性

想要对比接口不同实现在给定条件下的吞吐量大小

想要知道百分之N的请求在多长时间内完成

想要找出了热点函数,需要对热点函数进行进一步优化时

针对于函数的多种实现方式(例如JSON序列化/反序列化有Jackson和Gson实现),不知道哪种实现性能更好

JMH官方仓库:

GitHub - openjdk/jmh: https://openjdk.org/projects/code-tools/jmh

JMH官网示例demo:

code-tools/jmh: 2be2df7dbaf8 jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_01_HelloWorld.java

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

1、首先如果jdk小于1.9,则需要添加依赖

        org.openjdk.jmhjmh-core1.23org.openjdk.jmhjmh-generator-annprocess1.23

2、参考上面官网提供helloworld的demo

package org.openjdk.jmh.samples;import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;public class JMHSample_01_HelloWorld {@Benchmarkpublic void wellHelloThere() {}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JMHSample_01_HelloWorld.class.getSimpleName()).forks(1).build();new Runner(opt).run();}}

可知需要新建类,并在main方法中这样写去启动基准测试和执行测试。

然后需要进行测试的方法添加@Benchmark注解。

3、按照上面官网提供的示例,对比ArrayList与LinkedList在头部进行添加时的性能测试添加一些细节

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;//测试完成时间
@BenchmarkMode(Mode.AverageTime)
//设置统计结果的时间单位
@OutputTimeUnit(TimeUnit.NANOSECONDS)
//预热所需要配置的一些基本测试参数
@Warmup(iterations = 2,time = 1,timeUnit = TimeUnit.SECONDS)
//测试次数和时间
@Measurement(iterations = 5,time = 5,timeUnit = TimeUnit.SECONDS)
//fork一个线程
@Fork(1)
//通过 State 可以指定一个对象的作用范围
@State(Scope.Thread)
public class ArrayListWithLinkedListTestHeaderAdd {private static final int maxSize = 10000; //测试循环次数private static final int operationSize = 100; //操作次数private static ArrayList arrayList;private static LinkedList linkedList;public static void main(String[] args) throws RunnerException {//启动基准测试Options opt = new OptionsBuilder().include(ArrayListWithLinkedListTestHeaderAdd.class.getSimpleName())  //要导入的测试类.build();//执行测试new Runner(opt).run();}//@Setup作用于方法上,用于测试前的初始化工作@Setuppublic void init(){arrayList = new ArrayList();linkedList = new LinkedList();for (int i = 0; i < maxSize; i++) {arrayList.add(i);linkedList.add(i);}}//用于回收某些资源@TearDownpublic void finish(){}@Benchmarkpublic void addArrayByFirst(Blackhole blackhole){for (int i = 0; i < operationSize; i++) {arrayList.add(i,i);}//为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题blackhole.consume(arrayList);}@Benchmarkpublic void addLinkedByFirst(Blackhole blackhole){for (int i = 0; i < operationSize; i++) {linkedList.add(i,i);}//为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题blackhole.consume(linkedList);}
}

这里加了一些注解,并配置了一些参数。

4、JMH部分注解说明

@Benchmark

需要测试的方法,添加该注解。

@BenchmarkMode

用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行,如:

@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),还可以设置为 Mode.All,即全部执行一遍。

Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time

AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op

SampleTime:随机取样,最后输出取样结果的分布

SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能

All:上面的所有模式都执行一次

@State

通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。

@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。

由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能

Scope.Group:同一个线程在同一个 group 里共享实例

Scope.Thread:默认的 State,每个测试线程分配一个实例

@OutputTimeUnit

为统计结果的时间单位,可用于类或者方法注解

@Warmup

预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,

所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:

iterations:预热的次数

time:每次预热的时间

timeUnit:时间的单位,默认秒

batchSize:批处理大小,每次操作调用几次方法

为什么需要预热?

因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,

所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。

@Measurement

实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和 @Warmup 相同。

@Threads

每个进程中的测试线程,可用于类或者方法上。

@Fork

进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。

@Param

指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,

使用该注解必须定义@State 注解。

@Setup

方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。

@TearDown

方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等

5、运行上面的main方法,对比测试两个方法的性能

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部