您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java JMH 基准测试工具

时间:2020-09-07 10:33:20  来源:  作者:

任何新工具的出现,都是为了解决某个具体问题而诞生的,否则就没有存在的必要了

本文章将会概述一下 基准测试的概念 、StopWatch的基本使用、 JMH的基本使用与用户JMH中常用注解概述。

Java JMH 基准测试工具

 

 

1 引言

JMH 全称 JAVA Microbenchmark Harness ,Microbenchmark 可解析为 短语 micro-benchmark 测试,Microbenchmark也可解析为 micro(基本的)benchmark(标准检查程序) 。

JMH 是由 Java Jvm 虚拟机团队开发 ,在Jvm 对 Java 文件的编译阶段、类的加载阶段、运行阶段者有持续的不同程度的优化,JMH的诞生就是为了让 Java 开发者能够了解到自己所编写的代码运行的情况,以及性能方面的情况。

1.1 基准测试 ?

基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。

1.2 使用 StopWatch 来进行测试时间计算

一个常见的问题 就是 我们会说 ArrayList 比 LinkedList 性能好点,那么我们总会要想方法去测试一下,如添加 1000 0000 条数据,看谁消耗的时间少, StopWatch 用来记录这个时间差并可生成对比,如下代码清单 1-1 中所示的测试用例中,分别向 ArrayList 、LinkedList 中添加了 1000 0000 条数据,然后通过 StopWatch 来生成时间消耗对比:

///代码清单 1-1 
package com.example.demo;import org.junit.jupiter.api.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.util.StopWatch;import java.util.ArrayList;import java.util.LinkedList;import java.util.List;@SpringBootTestclass DemoApplicationTest2 {  private static final Logger LOG = LoggerFactory.getLogger(DemoApplicationTest2.class);
  @Test  void testArrayAndLinkedList() {    List<String> arrayList = new ArrayList<>();    StopWatch stopWatch = new StopWatch();    //开始计时
    stopWatch.start("arrayList 测试");
    for (int i = 0; i < 10000000; i++) {
      arrayList.add("测试数据");
    }    ///停止计时
    stopWatch.stop();        //测试 LinkedList
    List<String> linkedList = new LinkedList<>();    //开始计时
    stopWatch.start("linkedList 测试");
    for (int i = 0; i < 10000000; i++) {
      linkedList.add("测试数据");
    }    ///停止计时
    stopWatch.stop();            LOG.info("arrayList 消耗的总时间 " + stopWatch.prettyPrint());
    LOG.info("arrayList 消耗的总时间 " + stopWatch.getTotalTimeMillis());
  }  }

然后执行单元测试后生成 如下结果:

Java JMH 基准测试工具

 

很明显 对于add方法来讲,ArrayList 的性能要比 LinkedList 的性能要好点。

在这里只是一个粗糙的测试方法,因为:

  • 1. 使用到的 StopWatch ,在其内部也会记录方法的开始的纳秒数,这种操作也会消耗一定的CPU时间。
    2.JVM 在运行时对 for 循环也有优化,这样就会导致测试时间包含了一部分JVM性能优化的执行时间3.前后运行的 JVM 环境并不完全相同

所以为了能更严谨的来进行测试, JMH 就出现了。

2 JMH 基本使用

2.1 集成

JMH是 JDK9自带的,如果你是 JDK9 之前的版本也可以通过导入 openjdk

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
</dependency>

2.2 使用 JMH 进行测试

///代码清单 2-1
import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;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.List;import java.util.concurrent.TimeUnit;//Mode 表示 JMH 进行 Benchmark 时所使用的模式
//BenchmarkMode的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.Al
@BenchmarkMode(Mode.AverageTime)
//benchmark 结果所使用的时间单位
//使用java.util.concurrent.TimeUnit中的标准时间单位
// 微秒
@OutputTimeUnit(TimeUnit.MICROSECONDS)
///JMH测试类必须使用@State注解,
// State定义了一个类实例的生命周期,
// 可以类比Spring Bean的Scope
@State(Scope.Thread)
public class DemoApplicationTestJMH {
  public static void main(String[] args) throws Exception {
    String name = DemoApplicationTestJMH.class.getName();
    Options options = new OptionsBuilder()
        .include(name )
        .forks(1)
        .measurementIterations(3)
        .warmupIterations(3)
        .build();
    new Runner(options).run();
  }
  @Benchmark
  public void testArrayList() {
    List<String> arrayList = new ArrayList<>();
    for (int i = 0; i < 10000000; i++) {
      arrayList.add("测试数据");
    }
  }
  @Benchmark
  public void testLinkedList() {
    //测试 LinkedList
    List<String> linkedList = new LinkedList<>();
    for (int i = 0; i < 10000000; i++) {
      linkedList.add("测试数据");
    }
  }
}

然后运行main 方法后控制台日志会输出很长的日志信息,在这里是执行了testArrayList 与testLinkedList两个方法的基准测试,每个方法都会对应一段日志信息,小编在这里将testArrayList 方法 日志信息拆分成两段如下:

第一段包括 JVM 的启动参数配置信息 以及 JMH 的基本配置

/Library/Java/JavaVirtualmachines/jdk1.8.0_74.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=65371: ... 省略路径
IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath 
# JMH version: 1.19
# VM version: JDK 1.8.0_74, VM 25.74-b02
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=65371:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.example.demo.DemoApplicationTestJMH.testArrayList
# Run progress: 0.00% complete, ETA 00:00:12
# Fork: 1 of 1
objc[56915]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/jre/bin/java (0x10d1a44c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10d1e94e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 101153.611 us/op
# Warmup Iteration   2: 76302.787 us/op
# Warmup Iteration   3: 54296.903 us/op
Iteration   1: 57062.920 us/op
Iteration   2: 65024.286 us/op
Iteration   3: 56325.284 us/op

分析如下图

Java JMH 基准测试工具

 


Java JMH 基准测试工具

 


Warmup 可译为 预热的意思,在 JMH 中,Warmup 所做 的事情就是在基准测试代码正式执行测量(度量)前,对其进行预热,如 JVM运行器的编译、JIT 的优化等等
第二段 就是JMH 对 testArrayList 方法的测试输出信息了

Result "com.example.demo.DemoApplicationTestJMH.testArrayList":
  59470.830 ±(99.9%) 87999.595 us/op [Average]
  (min, avg, max) = (56325.284, 59470.830, 65024.286), stdev = 4823.555
  CI (99.9%): [≈ 0, 147470.424] (assumes normal distribution)
Java JMH 基准测试工具

 

然后 对于 testLinkedList 方法也会有 相同类似的日志信息只不过是输出的数据不一样,当两个方法执行基准测试完成后 最后会有对比信息日志如下:

Result "com.example.demo.DemoApplicationTestJMH.testLinkedList":
  206870.042 ±(99.9%) 2571061.260 us/op [Average]
  (min, avg, max) = (104680.987, 206870.042, 367640.921), stdev = 140928.543
  CI (99.9%): [≈ 0, 2777931.302] (assumes normal distribution)
# Run complete. Total time: 00:00:16
Benchmark                              Mode  Cnt       Score         Error  Units
DemoApplicationTestJMH.testArrayList   avgt    3   59470.830 ±   87999.595  us/op
DemoApplicationTestJMH.testLinkedList  avgt    3  206870.042 ± 2571061.260  us/op

2.3 对比

我期望与 代码清单 1-1 所使用的 StopWatch 计时对比一下时时间 ,StopWatch 中输出的是纳秒,对应的是 TimeUnit.NANOSECONDS ,所以我需要将 @OutputTimeUnit 配置的单位修改,以使用 JMH 度量后的时间单位输出为纳秒

@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class DemoApplicationTestJMH { ...}

再次运行度量测试 最终日志如下:

# Run complete. Total time: 00:00:16
Benchmark                              Mode  Cnt          Score           Error  Units
DemoApplicationTestJMH.testArrayList   avgt    3   57019569.485 ±  88169595.613  ns/op
DemoApplicationTestJMH.testLinkedList  avgt    3  368470033.556 ± 535285211.800  ns/op
Process finished with exit code 0

代码清单 1-1 所使用的 StopWatch 日志如下:

---------------------------------------------
ns         %     Task name
---------------------------------------------
177413718  044%  arrayList 测试
227298885  056%  linkedList 测试

3 参数概述

3.1 @BenchmarkMode

对应Mode选项,可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍

Mode 表示 JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。目前 JMH 共有四种模式:

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

3.2 Iteration 与 Warmup

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

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

3.3 @State

类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。

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

  • Scope.Thread:默认的State,每个测试线程分配一个实例;
  • Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
  • Scope.Group:每个线程组共享一个实例; ##### 3.4 @OutputTimeUnit 用来配置benchmark 结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。
TimeUnit.DAYS          //天  
TimeUnit.HOURS         //小时  
TimeUnit.MINUTES       //分钟  
TimeUnit.SECONDS       //秒  
TimeUnit.MILLISECONDS  //毫秒 
TimeUnit.NANOSECONDS   //毫微秒 纳秒
TimeUnit.MICROSECONDS  //微秒

3.5 其他

  @Benchmark

    方法注解,表示该方法是需要进行 benchmark 的对象。

  @Setup

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

  @TearDown

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

  @Param

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



Tags:Java JMH   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
任何新工具的出现,都是为了解决某个具体问题而诞生的,否则就没有存在的必要了本文章将会概述一下 基准测试的概念 、StopWatch的基本使用、 JMH的基本使用与用户JMH中常用注解...【详细内容】
2020-09-07  Tags: Java JMH  点击:(53)  评论:(0)  加入收藏
▌简易百科推荐
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(12)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(10)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(10)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(14)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(17)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
一、概述观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察...【详细内容】
2021-12-13  唯一浩哥    Tags:Java   点击:(16)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条