JMH学习笔记

JMH

1. 概述

  1. JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件
  2. JMH比较典型的应用场景有:
    • 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
    • 对比接口不同实现在给定条件下的吞吐量;
    • 查看多少百分比的请求在多长时间内完成;

简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。可以看出 JMH 主要使用在当你已经找出了热点函数,而需要对热点函数进行进一步的优化时,就可以使用 JMH 对优化的效果进行定量的分析。

2. 环境配置

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>${jmh.version}</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>${jmh.version}</version>
    <scope>provided</scope>
</dependency>

3. 基本概念

3.1 Mode

Mode 表示 JMH 进行 Benchmark 时所使用的模式。
目前 JMH 共有四种模式:

  1. Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
  2. AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
  3. SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
  4. SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。

3.2 Iteration

Iteration是JMH进行测试的最小单位。
大部分模式下,iteration代表的是一秒,JMH会在这一秒内不断调用需要benchmark的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。

3.3 Warmup

Warmup是指在实际进行Benchmark前先进行预热的行为。
因为JVM的JIT机制的存在,如果某个函数被调用多次以后,JVM会尝试将其编译成为机器码从而提高执行速度。所以为了让benchmark的结果更加接近真实情况就需要进行预热。

4. 常用注解

JMH 实现了JSR269规范,即注解处理器,能在编译Java源码的时候,识别的到需要处理的注解,如@Beanmark,JMH能根据@Beanmark的配置生成一系列测试辅助类。同例:流行开源Lombok 基于JSR269规范

4.1 @Benchmark

表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似

4.2 @BenchmarkMode

如之前所说,表示 JMH 进行 Benchmark 时所使用的模式。
可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍。

4.3 @State

State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为两种。

  • Scope.Thread: 默认的State,每个测试线程分配一个实例;
  • Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
  • Scope.Group:每个线程组共享一个实例;

4.4 @OutputTimeUnit

benchmark 结果所使用的时间单位。
使用java.util.concurrent.TimeUnit中的标准时间单位。

4.5 @Setup

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

4.6 @TearDown

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

4.7 @Param

成员注解,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。@Param注解接收一个String数组,在@setup方法执行前转化为为对应的数据类型。多个@Param注解的成员之间是乘积关系,譬如有两个用@Param注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。

4.8 @Warmup

@Warmup(iterations = 3)
配置预热次数,默认是每次运行1秒,运行10次,本例是运行3次。

4.9 @Measurement

@Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
配置执行次数,本例是一次运行5秒,总共运行3次。在性能对比时候,采用默认1秒即可,如果我们用jvisualvm做性能监控,我们可以指定一个较长时间运行。

4.10 @Threads(1)

Threads 配置同时起多少个线程执行,默认值是Runtime.getRuntime().availableProcessors(),本例启动1个线程同时执行

4.11 @Fork(1)

Fork,代表启动多个单独的进程分别测试每个方法,我们这里指定为每个方法启动一个进程。

5. 启动选项

public static void main(String[] args) throws RunnerException {
     Options opt = new OptionsBuilder()
             .include(JMHFirstBenchmark.class.getSimpleName())
             .forks(1)
             .warmupIterations(3)
             .measurementIterations(3)
             .build();

     new Runner(opt).run();
 }

include: benchmark 所在的类的名字。
fork: 进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。
warmupIterations:预热的迭代次数。
measurementIterations: 实际测量的迭代次数。

6. 代码清单

  1. 官方案例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值