JMH 简介

if 快还是 switch 快?HashMap 的初始化 size 要不要指定,指定之后性能可以提高多少?各种序列化方法哪个耗时更短?

无论出自何种原因需要进行性能评估,量化指标总是必要的。在大部分场合,简单地回答谁快谁慢是远远不够的,如何将程序性能量化呢?这就需要我们的主角 JMH 登场了!

JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。该工具是由 Oracle 内部实现 JIT 的大牛们编写的,他们应该比任何人都了解 JIT 以及 JVM 对于基准测试的影响。

当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用 JMH 对优化的结果进行量化的分析。JMH 比较典型的应用场景如下:

  • 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  • 对比接口不同实现在给定条件下的吞吐量
  • 查看多少百分比的请求在多长时间内完成

详细文档和示例参考文档: http://openjdk.java.net/projects/code-tools/jmh/

JMH是OpenJDK开发的,在JDK9中加入了JMH,之前版本需要通过如下方式引入:(目前 JMH 的最新版本为 1.23):

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

1、Benchmark测试用例的编写和执行

1)示例1:

这里写了两个@Benchmark,反序列化同一个常量JSON字符串到两个不同的Class,一个用LocalDateTime接收时间戳,另一个用Date接收,用的是业界最成熟的JSON库Jackson。

package cn.edu.nuc.Test;import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.Data;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 1)
@Measurement(iterations = 2)
@Threads(1)
@Fork(1)
@State(Scope.Thread)
public class Jackson2UtilBenchmark {private static String raw = "[{\"id\":1,\"name\":\"name1\",\"data\":[\"a\",\"b\",\"c\"]," +"\"createTime\":\"2019-05-09T01:53:13.396Z\",\"modifyTime\":\"2019-05-10T01:53:13.396Z\"}," +"{\"id\":2,\"name\":\"name2\",\"data\":[\"d\",\"e\",\"f\"],\"createTime\":\"2019-05-01T01:53:13.396Z\"," +"\"modifyTime\":\"2019-05-02T01:53:13.396Z\"}]";private ObjectMapper objectMapper = new ObjectMapper();private JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, SimplePojo.class);private JavaType javaTypeDate = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, SimplePojoDate.class);@Setup(Level.Trial)public void setup() {objectMapper.findAndRegisterModules();objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);}@Benchmarkpublic void parseLocalDateTime(Blackhole bh) throws IOException {bh.consume(objectMapper.readValue(raw, javaType));}@Benchmarkpublic void parseDate(Blackhole bh) throws IOException {bh.consume(objectMapper.readValue(raw, javaTypeDate));}public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().include(Jackson2UtilBenchmark.class.getSimpleName()).result("result1.json").resultFormat(ResultFormatType.JSON).build();new Runner(options).run();}
}@Data
class SimplePojo {private Integer id;private String name;private List<String> data;private LocalDateTime createTime;private LocalDateTime modifyTime;
}@Data
class SimplePojoDate {private Integer id;private String name;private List<String> data;private Date createTime;private Date modifyTime;
}

:运行上面代码前,需要使用mvn package,这样会在traget/下生成META-INF/BenchmarkList,否则会包如下错误;此外,代码必须要有包名,否则mvn package的时候会提示报错。

:jackson的版本需要是2.9.9,Java8增加了一套全新的日期时间类,Jackson对此也有支持。

2)示例2:字符串拼接的示例

package cn.edu.nuc.Test;
import java.util.concurrent.TimeUnit;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectBenchmark {@Param(value = {"10", "50", "100"})private int length;@Benchmarkpublic void testStringAdd(Blackhole blackhole) {String a = "";for (int i = 0; i < length; i++) {a += i;}blackhole.consume(a);}@Benchmarkpublic void testStringBuilderAdd(Blackhole blackhole) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {sb.append(i);}blackhole.consume(sb.toString());}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(StringConnectBenchmark.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build();new Runner(opt).run();}
}

说明:这里使用了@Param注解来轮换不同的参数

Benchmakr使用说明

1、常用注解

@Benchmark:每个带Benchmark注解的都是一个微基准测试用例

@BenchmarkMode:吞吐量或者平均时间等等,配合@OutputTimeUnit作为计量指标,该注解可以是一个数组,例如@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),value可取:

  • Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
  • AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
  • SampleTime:随机取样,最后输出取样结果的分布
  • SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
  • All:上面的所有模式都执行一次

@Fork:每个测试Fork进程执行,指定Fork的数量,这是为了防止JVM PGO(Profile-Guided Optimization)影响测试结果

@Threads:每个基准测试启动的并发线程数量

@State:通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

  • Thread表示变量只在同一个线程同步,一般用Thread,用到的成员变量相当于是ThreadLocal的
  • Benchmark表示变量整个测试的进程同步,比如方法A用到了变量B,线程C和线程D访问B是同一个变量需要加锁,而不是ThreadLocal的
  • Group则是同一组的测试使用同样的变量,配合@Group @GroupThreads 给关联的测试分组和同步使用

@Warmup @Measurement:预热和正式开始的轮数,每次迭代时间和循环次数都在这里配置,value可取

  • iterations:预热的次数
  • time:每次预热的时间
  • timeUnit:时间的单位,默认秒
  • batchSize:批处理大小,每次操作调用几次方法

@Setup @TearDown:这俩相当与JUnit4的@Before @After,做一些初始化和资源回收的事情,有3个Level

  • Trial 是默认值相当于 @BeforeClass @AfterClass
  • Iteration 表示每一轮预热或者正式测试都执行一次
  • Invocation 相当于 @Before @After,每次调用@Benchmark的函数都会执行一次,这个一般不要用,会干扰测试结果

@Param:轮换指定不同的调用参数测试

2、JMH 陷阱

在使用 JMH 的过程中,一定要避免一些陷阱。比如 JIT 优化中的死码消除,比如以下代码:

@Benchmark
public void testStringAdd(Blackhole blackhole) {String a = "";for (int i = 0; i < length; i++) {a += i;}
}

JVM 可能会认为变量 a 从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。JMH 提供了两种方式避免这种问题,一种是将这个变量作为方法返回值 return a,一种是通过 Blackhole 的 consume 来避免 JIT 的优化消除。

在benchmark测试时,要注意几下几点:

  • 某些数值计算在编译阶段就被算成常数了
  • 循环展开机制,编译器可能把循环300次自动优化成循环100次,每次循环递增调用3次等等
  • 一些没有副作用,返回值又没有赋值的函数,编译器或者JIT可能直接会整个函数调用都给直接省掉

其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用 Fork 隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的可以阅读 https://github.com/lexburner/JMH-samples 了解全部的陷阱。

3、JMH 可视化

除此以外,如果你想将测试结果以图表的形式可视化,可以试下这些网站:

  • JMH Visual Chart:http://deepoove.com/jmh-visual-chart/
  • JMH Visualizer:https://jmh.morethan.io/

比如将上面测试例子结果的 json 文件导入,就可以实现可视化:

参考:

https://mp.weixin.qq.com/s/k6t7yJLx73dWov835XVy3A

http://code2life.top/2019/05/11/0043-jmh-benchmark/

使用JMH编写基准测试相关推荐

  1. 【java】JMH微基准测试,报错Unable to find the resource: /META-INF/BenchmarkList

    1.概述 代码如下 package com.java.thread.demo.volatiled;import org.openjdk.jmh.annotations.*; import org.op ...

  2. SpringBoot集成JMH进行基准测试

    " 本地环境:IDEA 2018.3.6 " " jmh 1.21 .本地使用 1.22 失败,可能是 windows 10 下面使用了阿里巴巴的 Maven 镜像源有 ...

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

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

  4. 微基准测试 r_在您的构建过程中添加微基准测试

    微基准测试 r 介绍 作为一个行业,我们正在采用更高的透明度和更可预测的构建过程,以降低构建软件的风险. 持续交付的核心原则之一是通过反馈循环收集反馈. 在Dev9中 ,我们采用了与CD原则一致的&q ...

  5. 在您的构建过程中添加微基准测试

    介绍 作为一个行业,我们正在采用更高的透明度和更可预测的构建过程,以减少构建软件的风险. 持续交付的核心原则之一是通过反馈循环收集反馈. 在Dev9中 ,我们采用了与CD原则相一致的" 先知 ...

  6. java测试netty_《Netty官方文档》基准测试

    原文链接  译者:lijunshu Netty有一个模块叫'netty-microbench',我们可以用他来执行一系列的微型基准测试.Netty-microbench是基于OpenJDK JMH构件 ...

  7. Java 官方性能测试工具 JMH 简单入门

    什么是 JMH JMH 是 Java Microbenchmark Harness 的缩写.中文意思大致是 "JAVA 微基准测试套件".首先先明白什么是"基准测试&qu ...

  8. 性能调优必备利器之 JMH

    if 快还是 switch 快?HashMap 的初始化 size 要不要指定,指定之后性能可以提高多少?各种序列化方法哪个耗时更短? 无论出自何种原因需要进行性能评估,量化指标总是必要的. 在大部分 ...

  9. Java性能调优杀手锏JMH

    JMH简介 JMH(Java Microbenchmark Harness)由 OpenJDK/Oracle 里面那群开发了 Java编译器的大牛们所开发,是一个功能强大.灵活的工具,它可以用于检测和 ...

最新文章

  1. Redis的数据类型详解
  2. 6.1 无监督学习-机器学习笔记-斯坦福吴恩达教授
  3. 第十三节、SURF特征提取算法
  4. linux 以下命令对中正确的是什么,2016年Linux认证模拟真题及答案
  5. 仓库对象DataSet与小车对象DataAdapter的 关键命令 1201
  6. 2 华为云闪付_教你区分信用卡刷卡、挥卡、插卡、云闪付等支付方式!
  7. 【转载】网络通讯协议的国际斗争
  8. python怎么读取excel-python如何读写excel文件
  9. AutoFac IoC DI 依赖注入
  10. jQuery和$、jQuery(function(){})和(function(){})(jQuery)
  11. 自定义注解和注解的相关使用
  12. python 中用ts文件合并成为MP4
  13. 灰狼/狼群算法优化支持向量机SVM分类预测matlab代码,支持多分类。 Excel数据格式 ,直接运行 。
  14. Tomcat 服务详解
  15. Swift 之横竖屏切换
  16. contextmenu 鼠标右键菜单功能
  17. 如何对计算机进行磁盘整理,碎片整理,详细教您怎样进行磁盘碎片整理
  18. js 时间转东八区_JS时区时间转换详解
  19. 阿里云智能编码插件,Cosy文档搜索上新了
  20. Javaweb google身份宝验证

热门文章

  1. 在linux下面解压用的zxpf是什么意思,它跟zxvf有啥区别
  2. C语言程序设计主编张成叔,C语言程序设计教学做一体化教程(第2版)
  3. 双调和方程定解问题 | 分离变量法(八)| 偏微分方程(二十)
  4. python将输出保存为txt_Python3将数据保存为txt文件的方法
  5. mysql拼音搜索中文_mysql实现用拼音搜索中文的数据库实现
  6. slow post ddos tools torshammer (win32可执行下载)
  7. 如何通过SW Manage实现工程变更申请(ECR)
  8. 国外LEAD操作1个月就收款,副业还是得它
  9. 在Vue框架中通过a标签实现下载本地资源的功能
  10. python+flask 配置https网站ssl安全认证