本文摘抄自: https://hezhiqiang8909.gitbook.io/java/docs/javalib/jmh
JMH 应用指南
什么是 JMH
JMH 即Java Microbenchmark Harness,这是专门用于进行代码的微基准测试的一套工具API。
JMH由OpenJDK/Oracle里面那群开发了Java编译器的大牛们所开发。何谓Micro Benchmark呢?简单地说就是在method层面上的benchmark,精度可以精确到微妙级。
为什么需要 JMH
死码消除
所谓死码,是指注释的代码,不可达的代码块,可达但不被使用的代码等等。
常量折叠与常量传播
常量折叠 (Constant folding)是一个在编译时期简化常数的一个过程,常数在表示式中仅仅代表一个简单的数值,就像是整数2,若是一个变数从未被修改也可作为常数,或者直接将一个变数被明确地标注为常数。
JMH 的注意点
- 测试前需要预热
- 防止无用代码进入测试方法中
- 并发测试
- 测试结果呈现
应用场景
当你已经找出了热点函数,而需要对热点函数进行进一步的优化时,就可以使用JMH对优化的效果进行定量的分析。
想定量地知道某个函数需要执行多长时间,以及执行时间和输入n的相关性。
一个函数有两种不同实现(例如JSON序列化/反序列化有Jackson和Gson实现),不知道哪种实现性能更好
JMH 概念
Iteration
- iteration 是JMH进行测试的最小单位,包含一组 invocations 。Invocation
-一次 benchmark 方法调用。Operation
- benchmark 方法中,被测量操作的执行。如果被测试的操作在 benchmark 方法中循环执行,可以使用@OperationsPerInvocation
表明循环次数,使测试结果为单次 operatrion 的性能。Warmup
-在实际进行 benchmark 前先进行预热。因为某个函数被调用多次之后, JIT 会对其进行编译,通过预热可以使测量结果更加接近真实情况。
快速入门
添加 maven 依赖
1 | <dependency> |
测试代码
1 | import org.openjdk.jmh.annotations.*; |
执行 JMH
命令行
初始化 benchmarking 工程
1 | mvn archetype:generate \ |
构建 benchmark
1 | cd test/ |
运行 benchmark
1 | java -jar target/benchmarks.jar |
执行 main 方法
执行 main 方法,耐心等待测试结果,最终会生成一个测试报告,内容大致如下;
1 | # JMH version: 1.22 |
API
下面来了解一下 jmh 常用 API
@BenchmarkMode
基准测试类型。这里选择的是Throughput
也就是吞吐量。根据源码点进去,每种类型后面都有对应的解释,比较好理解,吞吐量会得到单位时间内可以进行的操作数。
Throughput
-整体吞吐量,例如”1秒内可以执行多少次调用”。AverageTime
-调用的平均时间,例如”每次调用平均耗时xxx毫秒”。SampleTime
-随机取样,最后输出取样结果的分布,例如”99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”SingleShotTime
-以上模式都是默认一次iteration是ls,唯有SingleShotTime是只运行一次。往往同时把warmup次数设为0,用于测试冷启动时的性能。All
- 所有模式
@Warmup
上面我们提到了,进行基准测试前需要进行预热。一般我们前几次进行程序测试的时候会比较慢,所以要让程序进行几轮预热,保证测试的准确性
@Measurement
度量,其实就是一些基本的测试参数。
iterations
-进行测试的轮次time
-每轮进行的时长timeUnit
-时长单位
都是一些基本的参数,可以根据具体情况调整。一般比较重的东西可以进行大量的测试,放到服务器上运行。
@Threads
每个进程中的测试线程,这个非常好理解,根据具体情况选择,一般为cpu乘以2。
@Fork
进行fork的次数。如果fork数是2的话,则JMH会fork出两个进程来进行测试。
@OutputTimeUnit
这个比较简单了,基准测试结果的时间类型。一般选择秒、毫秒、微妙。
@Benchmark
方法级注解,表示该方法是需要进行benchmark的对象,用法和JUnit的@Test类似。
@Param
属性级注解,@Param可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。
@Setup
方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。
@TearDown
方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。
@State
当使用@Setup参数的时候,必须在类上加上这个参数,不然会提示无法运行。
State用于声明某个类时一个”状态”,然后接受一个Scope参数用来表示该状态的共享范围。因为很多benchmark会需要一些表示状态的类,JMH允许你把这些类以依赖注入的方式注入到benchmark的函数里。Scope主要分为三种。
Thread
- 该状态为每个线程独享。Group
- 该状态为同一个组里面所有线程共享。Benchmark
- 该状态在所有线程间共享。
关于 State 的用法,官方的 code sample 里有比较好的例子 。
坑
- 执行应该用run模式而不是debug模式
- idea记得添加插件JMH Java Microbenchmark…
- 允许JMH能够对注解进行处理
compiler -> Annotation Processors -> Enable Annotation Processing