Table of Contents

一. 简介

二. 安装 [ idea plug ]

三. 注解

@Benchmark

@Warmup

@Measurement

@BenchmarkMode

@OutputTimeUnit

@State

@Param

@Threads

四.使用样例

4.1.修改pom.xml配置文件

4.2.测试map的循环输出效率

4.2.1.代码

4.2.2.结果:

4.2.3.原因.

4.3.测试list的循环效果

4.3.1.代码

4.3.2. 结果

五.官方网站&示例


一. 简介

JMH,全称 Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,是由 OpenJDK/Oracle 官方发布的工具。其精度可以达到毫秒级.可以执行一个函数需要多少时间,或者一个算法有多种不同实现等情况下,选择一个性能最好的那个.

Java的基准测试需要注意的几个点:

  • 测试前需要预热。
  • 防止无用代码进入测试方法中。
  • 并发测试。
  • 测试结果呈现。

预热?   为什么要预热? ???????????

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

JMH的使用场景:

  1. 定量分析某个热点函数的优化效果
  2. 想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性
  3. 对比一个函数的多种实现方式

二. 安装 [ idea plug ]

2.1 直接在Plugins的面板搜索 JMH  安装JMH plugin . 

 2.2 配置idea , 即可. 

三. 注解

@Benchmark

@Benchmark标签是用来标记测试方法的,只有被这个注解标记的话,该方法才会参与基准测试,但是有一个基本的原则就是被@Benchmark标记的方法必须是public的。

@Warmup

@Warmup用来配置预热的内容,可用于类或者方法上,越靠近执行方法的地方越准确。一般配置warmup的参数有这些:

  • iterations:预热的次数。

  • time:每次预热的时间。

  • timeUnit:时间单位,默认是s。

  • batchSize:批处理大小,每次操作调用几次方法。(后面用到)

@Measurement

用来控制实际执行的内容,配置的选项本warmup一样。

@BenchmarkMode

@BenchmarkMode主要是表示测量的维度,有以下这些维度可供选择:

  • Mode.Throughput 吞吐量维度

  • Mode.AverageTime 平均时间

  • Mode.SampleTime 抽样检测

  • Mode.SingleShotTime 检测一次调用

  • Mode.All 运用所有的检测模式 在方法级别指定@BenchmarkMode的时候可以一定指定多个纬度,例如: @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime}),代表同时在多个纬度对目标方法进行测量。

@OutputTimeUnit

@OutputTimeUnit代表测量的单位,比如秒级别,毫秒级别,微妙级别等等。一般都使用微妙和毫秒级别的稍微多一点。该注解可以用在方法级别和类级别,当用在类级别的时候会被更加精确的方法级别的注解覆盖,原则就是离目标更近的注解更容易生效。

@State

在很多时候我们需要维护一些状态内容,比如在多线程的时候我们会维护一个共享的状态,这个状态值可能会在每隔线程中都一样,也有可能是每个线程都有自己的状态,JMH为我们提供了状态的支持。该注解只能用来标注在类上,因为类作为一个属性的载体。 @State的状态值主要有以下几种:

  • Scope.Benchmark 该状态的意思是会在所有的Benchmark的工作线程中共享变量内容。
  • Scope.Group 同一个Group的线程可以享有同样的变量
  • Scope.Thread 每隔线程都享有一份变量的副本,线程之间对于变量的修改不会相互影响。

下面看两个常见的@State的写法:

1.直接在内部类中使用@State作为“PropertyHolder”public class JMHSample_03_States {@State(Scope.Benchmark)public static class BenchmarkState {volatile double x = Math.PI;}@State(Scope.Thread)public static class ThreadState {volatile double x = Math.PI;}@Benchmarkpublic void measureUnshared(ThreadState state) {state.x++;}@Benchmarkpublic void measureShared(BenchmarkState state) {state.x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JMHSample_03_States.class.getSimpleName()).threads(4).forks(1).build();new Runner(opt).run();}
}2.在Main类中直接使用@State作为注解,是Main类直接成为“PropertyHolder”
@State(Scope.Thread)
public class JMHSample_04_DefaultState {double x = Math.PI;@Benchmarkpublic void measure() {x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JMHSample_04_DefaultState.class.getSimpleName()).forks(1).build();new Runner(opt).run();}
}

我们试想以下@State的含义,它主要是方便框架来控制变量的过程逻辑,通过@State标示的类都被用作属性的容器,然后框架可以通过自己的控制来配置不同级别的隔离情况。被@Benchmark标注的方法可以有参数,但是参数必须是被@State注解的,就是为了要控制参数的隔离。

但是有些情况下我们需要对参数进行一些初始化或者释放的操作,就像Spring提供的一些init和destory方法一样,JHM也提供有这样的钩子:

  • @Setup 必须标示在@State注解的类内部,表示初始化操作

  • @TearDown 必须表示在@State注解的类内部,表示销毁操作

初始化和销毁的动作都只会执行一次。

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.Options;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class JMHSample_05_StateFixtures {double x;@Setuppublic void prepare() {System.out.println("This is Setup ");x = Math.PI;}@TearDownpublic void check() {System.out.println("This is TearDown ");assert x > Math.PI : "Nothing changed?";}@Benchmarkpublic void measureRight() {x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JMHSample_05_StateFixtures.class.getSimpleName()).forks(1).jvmArgs("-ea").build();new Runner(opt).run();}
}

虽然我们可以执行初始化和销毁的动作,但是总是感觉还缺点啥?对,就是初始化的粒度。因为基准测试往往会执行多次,那么能不能保证每次执行方法的时候都初始化一次变量呢? @Setup和@TearDown提供了以下三种纬度的控制:

  • Level.Trial 只会在个基础测试的前后执行。包括Warmup和Measurement阶段,一共只会执行一次。

  • Level.Iteration 每次执行记住测试方法的时候都会执行,如果Warmup和Measurement都配置了2次执行的话,那么@Setup和@TearDown配置的方法的执行次数就4次。

  • Level.Invocation 每个方法执行的前后执行(一般不推荐这么用)

@Param

在很多情况下,我们需要测试不同的参数的不同结果,但是测试的了逻辑又都是一样的,因此如果我们编写镀铬benchmark的话会造成逻辑的冗余,幸好JMH提供了@Param参数来帮助我们处理这个事情,被@Param注解标示的参数组会一次被benchmark消费到。

@State(Scope.Benchmark)
public class ParamTest {@Param({"1", "2", "3"})int testNum;@Benchmarkpublic String test() {return String.valueOf(testNum);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(ParamTest.class.getSimpleName()).forks(1).build();new Runner(opt).run();}
}

@Threads

测试线程的数量,可以配置在方法或者类上,代表执行测试的线程数量。

通常看到这里我们会比较迷惑Iteration和Invocation区别,我们在配置Warmup的时候默认的时间是的1s,即1s的执行作为一个Iteration,假设每次方法的执行是100ms的话,那么1个Iteration就代表10个Invocation。

四.使用样例

4.1.修改pom.xml配置文件

   <dependencies><!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.23</version></dependency><!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.23</version><scope>provided</scope></dependency></dependencies>

4.2.测试map的循环输出效率

4.2.1.代码

package com.map;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.Options;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashMapTest {static Map<Integer, String> map = new HashMap() {{// 添加数据for (int i = 0; i < 10000; i++) {put(i, "val:" + i);}}};public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(HashMapTest.class.getSimpleName()) // 要导入的测试类
//                .output("/a/jmh-map.log") // 输出测试结果的文件.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic void entrySet() {// 遍历Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<Integer, String> entry = iterator.next();Integer k = entry.getKey();String v = entry.getValue();}}@Benchmarkpublic void forEachEntrySet() {// 遍历for (Map.Entry<Integer, String> entry : map.entrySet()) {Integer k = entry.getKey();String v = entry.getValue();}}@Benchmarkpublic void keySet() {// 遍历Iterator<Integer> iterator = map.keySet().iterator();while (iterator.hasNext()) {Integer k = iterator.next();String v = map.get(k);}}@Benchmarkpublic void forEachKeySet() {// 遍历for (Integer key : map.keySet()) {Integer k = key;String v = map.get(k);}}@Benchmarkpublic void lambda() {// 遍历map.forEach((key, value) -> {Integer k = key;String v = map.get(k);});}@Benchmarkpublic void streamApi() {// 单线程遍历map.entrySet().stream().forEach((entry) -> {Integer k = entry.getKey();String v = entry.getValue();});}public void parallelStreamApi() {// 多线程遍历map.entrySet().parallelStream().forEach((entry) -> {Integer k = entry.getKey();String v = entry.getValue();});}
}

4.2.2.结果:

尽量使用  map.entrySet() 进行输出.  因为性能方面照普通的循环快将近2倍.

Benchmark                    Mode  Cnt      Score       Error  Units
HashMapTest.entrySet         avgt    5  47831.209 ± 14037.126  ns/op
HashMapTest.forEachEntrySet  avgt    5  56189.356 ± 16866.246  ns/op
HashMapTest.forEachKeySet    avgt    5  75973.584 ±  7738.707  ns/op
HashMapTest.keySet           avgt    5  76458.791 ± 11008.914  ns/op
HashMapTest.lambda           avgt    5  80734.554 ±  9695.806  ns/op
HashMapTest.streamApi        avgt    5  49939.633 ± 11131.095  ns/op

4.2.3.原因.

EntrySet 之所以比 KeySet 的性能高是因为,KeySet 在循环时使用了 map.get(key),而 map.get(key) 相当于又遍历了一遍 Map 集合去查询 key 所对应的值。为什么要用“又”这个词?那是因为在使用迭代器或者 for 循环时,其实已经遍历了一遍 Map 集合了,因此再使用 map.get(key) 查询时,相当于遍历了两遍

而 EntrySet 只遍历了一遍 Map 集合,之后通过代码“Entry<Integer, String> entry = iterator.next()”把对象的 key 和 value 值都放入到了 Entry 对象中,因此再获取 key 和 value 值时就无需再遍历 Map 集合,只需要从 Entry 对象中取值就可以了。

所以,EntrySet 的性能比 KeySet 的性能高出了一倍,因为 KeySet 相当于循环了两遍 Map 集合,而 EntrySet 只循环了一遍

4.3.测试list的循环效果

4.3.1.代码

package list;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.Options;import java.util.*;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class ListTest {static LinkedList<Integer> linkedList = new LinkedList<Integer>(){{// 添加数据for (int i = 0; i < 10000; i++) {add(i);}}};static ArrayList<Integer> arrayList = new ArrayList<Integer>(){{// 添加数据for (int i = 0; i < 10000; i++) {add(i);}}};public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(ListTest.class.getSimpleName()) // 要导入的测试类
//                .output("/a/jmh-map.log") // 输出测试结果的文件.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic void linkedListFor() {// 遍历for (int i = 0 ; i < linkedList.size() ; i++) {int x = linkedList.get(i);}}@Benchmarkpublic void linkedListForEach() {// 遍历for (Integer i  : linkedList ) {}}@Benchmarkpublic void arrayListFor() {// 遍历for (int i = 0 ; i < arrayList.size() ; i++) {int x = arrayList.get(i);}}@Benchmarkpublic void arrayListForEach() {// 遍历for (Integer i  : arrayList ) {}}}

4.3.2. 结果

Benchmark                   Mode  Cnt         Score         Error  Units
ListTest.arrayListFor       avgt    5      7291.515 ±    1348.462  ns/op
ListTest.arrayListForEach   avgt    5      6955.242 ±    1587.509  ns/op
ListTest.linkedListFor      avgt    5  41467520.339 ± 8443367.361  ns/op
ListTest.linkedListForEach  avgt    5     22096.751 ±    6459.100  ns/op

五.官方网站&示例

官网: https://openjdk.java.net/projects/code-tools/jmh/

git hub idea-jmh-plugin : https://github.com/artyushov/idea-jmh-plugin

示例: https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

基准测试神器 - JMH [ Java Microbenchmark Harness ]相关推荐

  1. benchmark java_java使用JMH Java Microbenchmark Harness性能测试 | 程序那些事

    在java中使用JMH(Java Microbenchmark Harness)做性能测试 JMH的全称是Java Microbenchmark Harness,是一个open JDK中用来做性能测试 ...

  2. JMH(Java Microbenchmark Harness, java微基准测试工具)

    JMH是什么 JMH是Java Microbenchmark Harness(java 微基准测试工具)的简称,是openJDK的一部分. JMH VS JMeter 看来简单比较下他们俩.JMete ...

  3. 笔记-JMH(Java Microbenchmark Harness)

    更多请移步我的博客 看开源项目时,时不常遇到一个叫做benchmark的目录,此时脑子停滞,一眼带过,最近一次看到就顺手问了下谷大哥,发现benchmark还是个挺有意思的东西. 基准测试是什么 基准 ...

  4. JMH(Java Microbenchmark Harness) Java微基准测试

    官网:OpenJDK: jmh 什么是JMH?微基准测试,他是测的某一个方法的性能到底是好或者不好,换了方法的实现之后他的性能到底好还是不好 创建JMH测试 创建Maven项目,添加依赖,我们需要添加 ...

  5. 在java中使用JMH(Java Microbenchmark Harness)做性能测试

    文章目录 使用JMH做性能测试 BenchmarkMode Fork和Warmup State和Scope 在java中使用JMH(Java Microbenchmark Harness)做性能测试 ...

  6. Java Microbenchmark Harness (JMH)

    Java Microbenchmark Harness (JMH) http://albertnetymk.github.io/2017/12/27/jmh/(此文档是安装过程) https://gi ...

  7. benchmark java_在java中使用JMH(Java Microbenchmark Harness)做性能测试

    JMH的全称是Java Microbenchmark Harness,是一个open JDK中用来做性能测试的套件.该套件已经被包含在了JDK 12中. 本文将会讲解如何使用JMH来在java中做性能 ...

  8. Java 并发测试神器:基准测试神器-JMH

    点击上方"IT牧场",选择"设为星标"技术干货每日送达! 来源:吕彦峰 sq.163yun.com/blog/article/17967196048178380 ...

  9. 基准测试神器JMH——详解36个官方例子

    本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 简介 基准测试是指通过设计科学的测试方法.测试工具和测试系统,实现对一类测试对象的某项性能指标 ...

最新文章

  1. Centos7 下安装python3及卸载
  2. 2020年,你读到印象最深的论文是哪篇?
  3. 墨菲的鬼魂重现 / 金蝶ERP 资安大漏洞 (非授权补丁自动下载安装)
  4. 深度探索C++ 对象模型(4)-Default Copy Constructor(2)
  5. mysql timestamp 差值_MySQL中TIMESTAMPDIFF和TIMESTAMPADD函数的用法(两个日期之间的差值)...
  6. 重温c语言之环境变量
  7. 体验 ASP.NET Core 1.1 中预编译 MVC Razor 视图
  8. 监听某个区域滚动_监听页面滚动及滚动到指定位置
  9. [导入].Net 中处理Word(2007)文档的一种方法
  10. 贝叶斯分类器与贝叶斯网络
  11. html中写一个占内存很大死循环代码,HTML中的循环
  12. 滚动后mouseleave失效_大型调心滚子轴承偏载失效案例详情
  13. [Java] 蓝桥杯BASIC-19 基础练习 完美的代价
  14. 计算流体力学漫谈-1 (可压缩向)
  15. 1实训(学生信息管理系统)
  16. word里显示的这个向下箭头是什么意思
  17. 【免费】如何轻松的从音乐网站下载自己喜欢的mp3音乐?
  18. linux c++ 文件修改时间,linux下C获取系统时间的方法
  19. 3c认证是什么,3c认证产品范围与认证材料
  20. Cornerstone忽略不必要的文件

热门文章

  1. 项目开发 | 转载 | 需求评审与技术评审
  2. lammps教程:薄膜渗透模拟(3)--不同孔隙率对过滤效果的影响
  3. stf、atx、sonic云真机平台使用与对比
  4. 理论计算机科学逻辑博导,清华大学计算机科学与技术系博士生导师简介:应明生...
  5. 艺术家与AI研究者的跨界碰撞丨记青源Workshop「AI+艺术」研讨会(2022年第10期)...
  6. 电路基础(3)电阻电路等效变换的经典例题
  7. 关于原创文章特此说明
  8. linux 变量引用 和 变量的自动类型转换 c++,C++能不能让编译器自动推导变量类型吗...
  9. NETPLIER : 一款基于概率的网络协议逆向工具(一)理论
  10. 运用计算机通过动力方程,计算机技术在地下水动力学课程素材建设中的应用