一、JMH 概述
JMH (Java Micro Benchmark Harness) ,java 微基准测试工具。
JMH 是由实现 Java 虚拟机的团队开发的,用于基准测试。
二、JMH 工具基础使用
2.1、简单案例
以简单的代码作为讲解:对比 LinkedList#add 和 ArrayList#add 性能
基础代码如下:
/**
* JMH 测试:微基准测试 HashMap 和 LinkedHashMap add 性能
* @author zhixing
* @since V2.2
*/
@Warmup(iterations = 3) // 预热批次 3 ,不计入度量统计
@Measurement(iterations = 3) // 度量批次 3 ,计入度量统计
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTest {
private final static String DATA = "TEST DATA";
private Map<String,String> hashMap;
private Map<String,String> linkedHashMap;
@Setup(Level.Iteration)
public void setUp(){
hashMap = new HashMap<>();
linkedHashMap = new LinkedHashMap<>();
}
@Benchmark
public Map<String,String> hashMapPut(){
this.hashMap.put(DATA,DATA);
return this.hashMap;
}
@Benchmark
@Warmup(iterations = 5) // 预热批次 5 ,不计入度量统计
@Measurement(iterations = 5) // 度量批次 5 ,计入度量统计
public Map<String,String> linkedHashMapPut(){
this.linkedHashMap.put(DATA,DATA);
return this.linkedHashMap;
}
public static void main(String[] args) throws RunnerException {
final Options opts = new OptionsBuilder().include(JmhTest.class.getSimpleName()).forks(1)
.measurementIterations(2) // 度量执行批次为2,度量数据会纳入统计
.warmupIterations(2) // 度量前预热次数为2,不纳入统计
.build();
new Runner(opts).run();
}
}
代码运行结果(只看最后两行)
- HashMap#put 方法调用平均响应时间为:0.006微秒
- LinkedHashMap#put 方法调用平均响应时间为:0.007 微秒
三、JMH 使用简要说明
3.1、@Benchmark
类似 @Test单元测试,@Benchmark 标注方法为需要基准测试的方法
3.2、Warmup 和 Measurement
Warmup 概述
直译为 “预热”,Warmup 工作是在基准测试代码正式度量前,对其进行预热,使得代码的执行经过了类的早期优化、JVM运行期编译、JIT优化后的最终状态,使得代码性能测试数据更准确。
Measurement概述
直译为“度量”,是真正的度量操作,每一轮的度量,所有的度量数据会被纳入统计之中(预热数据不会纳入统计之中)。
3.2.1、全局设置 Warmup 和 Measurement
两种方式设置:
- 构造 Options 时设置
- class 上加注解
3.2.2、针对基准方法局部设置 Warmup 和 Measurement
1.26 版本优先级:全局 Options 设置 > 局部设置 > 全局注解设置
3.2.3、Warmup 和 Measurement 输出结果解析
## 使用的 JMH 版本:1.26
# JMH version: 1.26
## JDK 版本信息
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
## JAVA 命令目录
# VM invoker: D:\2-software\15-jdk8\jdk\jre\bin\java.exe
## JAVA 运行时指定参数
# VM options: -javaagent:D:\2-software\9-idea-2019-1\lib\idea_rt.jar=51465:D:\2-software\9-idea-2019-1\bin -Dfile.encoding=UTF-8
## 热身批次 2,每次执行 10s
# Warmup: 2 iterations, 10 s each
## 度量测试批次 2 ,每次执行 10s
# Measurement: 2 iterations, 10 s each
## 每一个批次的超时时间
# Timeout: 10 min per iteration
## 执行基准测试的线程数量
# Threads: 1 thread, will synchronize iterations
## 统计标准:方法调用一次所耗费的单位时间
# Benchmark mode: Average time, time/op
## Benchmark 方法的绝对历经
# Benchmark: com.zhixing.jmh.JmhTest.hashMapPut
## 执行进度
# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 1
## 第一次热身平均耗时:0.007 微秒
# Warmup Iteration 1: 0.007 us/op
## 第二次热身评论耗时:0.007 微秒
# Warmup Iteration 2: 0.007 us/op
## 度量批次调用:2个批次
Iteration 1: 0.006 us/op
Iteration 2: 0.006 us/op
## 最终统计结果:调用一次方法平均耗费 0.006 微秒
Result "com.zhixing.jmh.JmhTest.hashMapPut":
0.006 us/op
## 同上
# JMH version: 1.26
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: D:\2-software\15-jdk8\jdk\jre\bin\java.exe
# VM options: -javaagent:D:\2-software\9-idea-2019-1\lib\idea_rt.jar=51465:D:\2-software\9-idea-2019-1\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.zhixing.jmh.JmhTest.linkedHashMapPut
# Run progress: 50.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration 1: 0.007 us/op
# Warmup Iteration 2: 0.007 us/op
Iteration 1: 0.007 us/op
Iteration 2: 0.007 us/op
Result "com.zhixing.jmh.JmhTest.linkedHashMapPut":
0.007 us/op
# Run complete. Total time: 00:01:21
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
## 最后的结果对比
Benchmark Mode Cnt Score Error Units
JmhTest.hashMapPut avgt 2 0.006 us/op
JmhTest.linkedHashMapPut avgt 2 0.007 us/op
3.3、@BenchmarkMode
用来声明使用那种模式运行,JMH 提供了四种模式
- AverageTime(平均响应时间)
基准测试方法每一次调用的平均耗时- Throughput(方法吞吐量)
单位时间内能够对基准方法调用多少次- SampleTime(时间采样)
抽样统计基准测试方法性能- SingleShotTime
冷测试
3.3.1、AverageTime(平均响应时间)
3.3.2、Throughput(方法吞吐量)
class 设置注解
执行结果
- hashMapPut 每毫秒调用 156551.656 次
- linkedHashMapPut 每毫秒调用 150773.914
3.3.3、SampleTime(时间采样)
class 设置注解
执行结果3.3.4、SingleShotTime
class 设置注解预热批次设为 0
3.4、@OutputTimeUnit
设置统计输出时的单位:通过 TimeUnit 设置,单位由:秒、毫秒等。 可以通过
- 注解
- Options 参数配置
3.5、@State
State 中的 scope 对应三个值
- Thread
- Group
- Benchmark
3.5.1、Thread
表示线程独享,每一个运行基准测试方法的线程都会持有一个独立的对象实例,该实例既可能作为基准测试方法参数传入的,也可能是运行基准方法所在的宿主 class 。 针对非线程安全的类使用。
验证关键代码如下:
JmhTest.java
其他方式JmhDemo.java
- @State(Scope.Thread) 设置线程独享
- @Threads(5) 设置 5 个线程运行基准测试方法
- 无参构造打印信息验证
执行结果
每一个基准测试方法由 5 个线程测试,每个线程持有各自的实例对象。
3.5.2、Benchmark
对象实例被所有的线程共享
验证关键代码JmhDemo.java
- 设置 5 个线程
- 设定对象线程共享
- 无参构造打印,方便测试
3.5.3、Group
在多线程的情况下,允许多一个实例的多个基准测试方法同时进行操作
验证关键代码JmhDemo.java
**
执行结果
# JMH version: 1.26
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: D:\2-software\15-jdk8\jdk\jre\bin\java.exe
# VM options: -javaagent:D:\2-software\9-idea-2019-1\lib\idea_rt.jar=54745:D:\2-software\9-idea-2019-1\bin -Dfile.encoding=UTF-8
# Warmup: <none>
# Measurement: 3 iterations, 10 s each
# Timeout: 10 min per iteration
## 总共 6 个线程,在同一个 group 中,3个调用 read 方法,3个调用 write 方法
# Threads: 6 threads (1 group; 3x "read", 3x "write" in each group), will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.zhixing.jmh.JmhDemo.test
# Run progress: 0.00% complete, ETA 00:00:30
# Fork: 1 of 1
Iteration 1: create instance
write
read
write
write
............. 省略read write 交替打印
read
read
0.078 ±(99.9%) 0.047 ms/op
read: 0.074 ±(99.9%) 0.165 ms/op
write: 0.082 ±(99.9%) 0.432 ms/op
Result "com.zhixing.jmh.JmhDemo.test":
0.083 ±(99.9%) 0.209 ms/op [Average]
(min, avg, max) = (0.076, 0.083, 0.097), stdev = 0.011
CI (99.9%): [≈ 0, 0.292] (assumes normal distribution)
Secondary result "com.zhixing.jmh.JmhDemo.test:read":
0.086 ±(99.9%) 0.446 ms/op [Average]
(min, avg, max) = (0.071, 0.086, 0.115), stdev = 0.024
CI (99.9%): [≈ 0, 0.532] (assumes normal distribution)
Secondary result "com.zhixing.jmh.JmhDemo.test:write":
0.081 ±(99.9%) 0.033 ms/op [Average]
(min, avg, max) = (0.079, 0.081, 0.082), stdev = 0.002
CI (99.9%): [0.047, 0.114] (assumes normal distribution)
# Run complete. Total time: 00:00:30
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark Mode Cnt Score Error Units
JmhDemo.test avgt 3 0.083 ± 0.209 ms/op
JmhDemo.test:read avgt 3 0.086 ± 0.446 ms/op
JmhDemo.test:write avgt 3 0.081 ± 0.033 ms/op
3.6、@Param
提供配置化参数 案例:提供配置参数,一个基准测试方法,同时测试同种类型的Map 关键代码如下JmhDemo.java
执行结果
Tips:type 为我们设定参数配置,对应不同的容器实例
3.7、JMH测试套件-Fixture
Fixture 的作用类似于单元测试提供的 @Before
@After
之类的注解使用。
3.7.1、 @Setup
和 @TearDown
@Setup
在每个基准测试方法调用前执行。通常用于资源初始化。@TearDown
在每个基准测试方法调用后执行。通常用于资源回收。
Level
用来控制 @Setup
和 @TearDown
的执行。Level
三个级别
- Trial
@Setup
和@TearDown
默认配置,表示两个方法会在基准测试方法的所有批次执行前后执行。 - Iteration
在设置了Warmup
和Measurement
后,预热和度量批次会执行多次。
该配置能够使得在每个批次执行前后执行@Setup
和@TearDown
标注的方法。 - Invocation
在每一次度量操作中,针对基准测试方法的每一次调用前后都会执行@Setup
和@TearDown
标注的方法
3.8、@CompilerControl(不推荐)
禁止JVM 运行期间针对方法的优化
小贴士:其他两种方式,禁止 JVM 运行期的优化操作
- 代码编写
java.lang.Compiler.disable();- JVM 参数设置
-Djava.compiler=NONE