导言
大家好,我是南橘,从接触java到现在也有差不多两年时间了,两年时间,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助
有需要的同学可以加我的公众号,以后的最新的文章第一时间都在里面,也可以找我要思维导图
前两章介绍了JAVA代码调优的一些方法,这一章我们就一起学习一下代码调优时的测试工具JMH
一、JMH介绍
JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
应用场景
- 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
- 对比接口不同实现在给定条件下的吞吐量
- 查看多少百分比的请求在多长时间内完成
具体内容可以查看JMH的官方DEMO JMH官方DEMO
二、JMH使用注解总结
1、BenchmarkMode
JMH测试时运行的模式模式
| 类型 | 效用 |
|---|---|
| Mode.All | 所有模式依次运行(测试时使用最多) |
| Mode.Throughput | 每段时间执行的次数,一般是秒 |
| Mode.AverageTime | 平均时间,每次操作的平均耗时 |
| Mode.SampleTime | 在测试中,随机进行采样执行的时间 |
| Mode.SingleShotTime | 在每次执行中计算耗时 |
可以组合使用
2、OutputTimeUnit
用来决定JMH测试的指定时间单位,它需要一个标准Java类型java.util.concurrent.TimeUnit作为参数。
3、Warmup
进行基准测试前需要进行预热。
在进行微基准测试时,我们想要测试的是“程序被JVM编译成机器代码(而不是直接执行字节码)”的执行速度。为了让JVM把要测试的代码编译成机器码,我们可能需要把要测试的代码进行“预热处理”(就是先跑几回,或十几回等,当运行的次多了,JVM就会生成机器码),JMH就提供“预热处理”等一系列的处理。
4、State
注解定义了给定类实例的可用范围
5、Measurement
给定基本的测试参数。
- iterations进行测试的轮次
- time每轮进行的时长
- timeUnit时长单位
6、Fork
需要运行的试验(迭代集合)数量。每个试验运行在单独的JVM进程中。也可以指定(额外的)JVM参数。
7、Threads
该测试使用的线程数。默认是Runtime.getRuntime().availableProcessors()
8、Benchmark
方法注解,表示该方法是需要进行 benchmark 的对象。
9、Setup
方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
10、TearDown
与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。
11、Param
成员注解,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。@Param注解接收一个String数组,在@setup方法执行前转化为为对应的数据类型。多个@Param注解的成员之间是乘积关系,譬如有两个用@Param注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。
三、JMH使用
1、添加依赖
因为 JMH 是 JDK9 自带的,如果是 JDK9 之前的版本需要加入依赖(人人都用JAVA8)
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version></version>()
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version></version>
</dependency>
2、代码演示
先找到之前做过测试的代码
public class CommonUtils {
static int cacheSize =1024;
static String[] caches =new String[cacheSize];
static {
for(int i=0;i<cacheSize;i++){
caches[i]=String.valueOf(i);
}
}
public static String int2String(int data){
if (data<cacheSize){
return caches[data];
}else{
return String.valueOf(data);
}
}
}
@BenchmarkMode(Mode.All) // 运行所有模式
@Warmup(iterations = 3) //预热轮次
@Measurement(iterations = 3,time = 1,timeUnit = TimeUnit.SECONDS)//基本的测试参数
@Threads(1) //线程数
@Fork(1) // 迭代集合进行的数目
@OutputTimeUnit(TimeUnit.MILLISECONDS) //指定时间单位
@State(Scope.Benchmark) //运行所有测试的线程共享实例
public class Test1 {
@Param({"1","55","999","1023","4444"})
int status;
@Benchmark
public String int2String(){
return String.valueOf(status);
}
@Benchmark
public String int2StringByCache(){
return CommonUtils.int2String(status);
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder().include(Test1.class.getSimpleName()).build();
new Runner(options).run();
}
}
点击运行得出结论
- 1、我们可以分别看到 两个方法依次对Param里的参数进行了测试。
- 2、Mode里的参数thrpt是Throughput的简写
- 3、Cnt表示测试了三遍
- 4、Score通过对Mode的参数的解读,得出每毫秒执行的次数(ops/ms)
- 5、Error表示测试的误差,测试轮次越多误差越小(大概吧)
四、JMH 插件
大家还可以通过 IDEA 安装 JMH 插件使 JMH 更容易实现基准测试,在 IDEA 中点击 File->Settings...->Plugins,然后搜索 jmh,选择安装 JMH plugin:
这个插件可以让我们能够以 JUnit 相同的方式使用 JMH,主要功能如下:
- 自动生成带有 @Benchmark 的方法像 JUnit 一样
- 运行单独的 Benchmark 方法
- 运行类中所有的 Benchmark 方法
比如可以通过右键点击 Generate.,选择操作 Generate JMH benchmark 就可以生成一个带有 @Benchmark 的方法。
还有将光标移动到方法声明并调用 Run 操作就运行一个单独的 Benchmark 方法。
将光标移到类名所在行,右键点击 Run 运行,该类下的所有被 @Benchmark 注解的方法都会被执行。
结语
我们在编写代码的过程中,稍稍一注意,就能全面提升代码的性能。本文主要介绍了性能基准测试工具 JMH,它可以通过一些功能来规避由 JVM 中的 JIT 或者其他优化对性能测试造成的影响。只需要将待测的业务逻辑用 @Benchmark 注解标识,就可以让 JMH 的注解处理器自动生成真正的性能测试代码,以及相应的性能测试配置文件。
同时需要思维导图的话,可以联系我,毕竟知识越分享越香!