前言

“If you cannot measure it, you cannot improve it”.

在日常开发中,我们对一些代码的调用或者工具的使用会存在多种选择方式,在不确定他们性能的时候,我们首先想要做的就是去测量它。大多数时候,我们会简单的采用多次计数的方式来测量,来看这个方法的总耗时。

但是,如果熟悉JVM类加载机制的话,应该知道JVM默认的执行模式是JIT编译与解释混合执行。JVM通过热点代码统计分析,识别高频方法的调用、循环体、公共模块等,基于JIT动态编译技术,会将热点代码转换成机器码,直接交给CPU执行。

也就是说,JVM会不断的进行编译优化,这就使得很难确定重复多少次才能得到一个稳定的测试结果?所以,很多有经验的同学会在测试代码前写一段预热的逻辑。

JMH,全称 Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,是由 OpenJDK/Oracle 官方发布的工具。何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。

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

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

JMH的使用场景:

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

本篇主要是介绍JMH的DEMO演示,和常用的注解参数。希望能对你起到帮助。

DEMO 演示

这里先演示一个DEMO,让不了解JMH的同学能够快速掌握这个工具的大概用法。

1.测试项目构建

JMH是内置Java9及之后的版本。这里是以Java8进行说明。

为了方便,这里直接介绍使用maven构建JMH测试项目的方式。

第一种是使用命令行构建,在指定目录下执行以下命令:

$ mvn archetype:generate \-DinteractiveMode=false \-DarchetypeGroupId=org.openjdk.jmh \-DarchetypeArtifactId=jmh-java-benchmark-archetype \-DgroupId=org.sample \-DartifactId=test \-Dversion=1.0

对应目录下会出现一个test项目,打开项目后我们会看到这样的项目结构。

第二种方式就是直接在现有的maven项目中添加jmh-core和jmh-generator-annprocess的依赖来集成JMH。

   <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>

2.编写性能测试

这里我以测试LinkedList 通过index 方式迭代和foreach 方式迭代的性能差距为例子,编写测试类,涉及到的注解在之后会讲解,

/*** @author Richard_yyf* @version 1.0 2019/8/27*/@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {private static final int SIZE = 10000;private List<String> list = new LinkedList<>();@Setuppublic void setUp() {for (int i = 0; i < SIZE; i++) {list.add(String.valueOf(i));}}@Benchmark@BenchmarkMode(Mode.Throughput)public void forIndexIterate() {for (int i = 0; i < list.size(); i++) {list.get(i);System.out.print("");}}@Benchmark@BenchmarkMode(Mode.Throughput)public void forEachIterate() {for (String s : list) {System.out.print("");}}
}

3.执行测试

运行 JMH 基准测试有两种方式,一个是生产jar文件运行,另一个是直接写main函数或者放在单元测试中执行。

生成jar文件的形式主要是针对一些比较大的测试,可能对机器性能或者真实环境模拟有一些需求,需要将测试方法写好了放在linux环境执行。具体命令如下

$ mvn clean install
$ java -jar target/benchmarks.jar

我们日常中遇到的一般是一些小测试,比如我上面写的例子,直接在IDE中跑就好了。启动方式如下:

 public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(LinkedListIterationBenchMark.class.getSimpleName()).forks(1).warmupIterations(2).measurementIterations(2).output("E:/Benchmark.log").build();new Runner(opt).run();}

4. 报告结果

输出结果如下,

最后的结果:

Benchmark                                      Mode  Cnt     Score   Error  Units
LinkedListIterationBenchMark.forEachIterate   thrpt    2  1192.380          ops/s
LinkedListIterationBenchMark.forIndexIterate  thrpt    2   206.866          ops/s

整个过程:

# Detecting actual CPU count: 12 detected
# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forEachIterate# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 1
# Warmup Iteration   1: 1189.267 ops/s
# Warmup Iteration   2: 1197.321 ops/s
Iteration   1: 1193.062 ops/s
Iteration   2: 1191.698 ops/sResult "org.sample.jmh.LinkedListIterationBenchMark.forEachIterate":1192.380 ops/s# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate# Run progress: 50.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration   1: 205.676 ops/s
# Warmup Iteration   2: 206.512 ops/s
Iteration   1: 206.542 ops/s
Iteration   2: 207.189 ops/sResult "org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate":206.866 ops/s# Run complete. Total time: 00:01:21REMEMBER: 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
LinkedListIterationBenchMark.forEachIterate   thrpt    2  1192.380          ops/s
LinkedListIterationBenchMark.forIndexIterate  thrpt    2   206.866          ops/s

注解介绍

下面我们来详细介绍一下相关的注解,

@BenchmarkMode

微基准测试类型。JMH 提供了以下几种类型进行支持:

类型 描述
Throughput 每段时间执行的次数,一般是秒
AverageTime 平均时间,每次操作的平均耗时
SampleTime 在测试中,随机进行采样执行的时间
SingleShotTime 在每次执行中计算耗时
All 所有模式

可以注释在方法级别,也可以注释在类级别,

@BenchmarkMode(Mode.All)
public class LinkedListIterationBenchMark {...
}
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
public void m() {...
}

@Warmup

这个单词的意思就是预热,iterations = 3就是指预热轮数。

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Warmup(iterations = 3)
public void m() {...
}

@Measurement

正式度量计算的轮数。

  • iterations 进行测试的轮次
  • time 每轮进行的时长
  • timeUnit时长单位
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Measurement(iterations = 3)
public void m() {...
}

@Threads

每个进程中的测试线程。

@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {...
}

@Fork

进行 fork 的次数。如果 fork 数是3的话,则 JMH 会 fork 出3个进程来进行测试。

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Fork(value = 3)
public void m() {...
}

@OutputTimeUnit
基准测试结果的时间类型。一般选择秒、毫秒、微秒。

@OutputTimeUnit(TimeUnit.SECONDS)
public class LinkedListIterationBenchMark {...
}

@Benchmark

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

@Param

属性级注解,@Param 可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。

@Setup

方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。

@TearDown

方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。

@State

当使用@Setup参数的时候,必须在类上加这个参数,不然会提示无法运行。

就比如我上面的例子中,就必须设置state。

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

  • Thread: 该状态为每个线程独享。
  • Group: 该状态为同一个组里面所有线程共享。
  • Benchmark: 该状态在所有线程间共享。

启动方法

在启动方法中,可以直接指定上述说到的一些参数,并且能将测试结果输出到指定文件中,

  /*** 仅限于IDE中运行* 命令行模式 则是 build 然后 java -jar 启动** 1. 这是benchmark 启动的入口* 2. 这里同时还完成了JMH测试的一些配置工作* 3. 默认场景下,JMH会去找寻标注了@Benchmark的方法,可以通过include和exclude两个方法来完成包含以及排除的语义*/public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder()// 包含语义// 可以用方法名,也可以用XXX.class.getSimpleName().include("Helloworld")// 排除语义.exclude("Pref")// 预热10轮.warmupIterations(10)// 代表正式计量测试做10轮,// 而每次都是先执行完预热再执行正式计量,// 内容都是调用标注了@Benchmark的代码。.measurementIterations(10)//  forks(3)指的是做3轮测试,// 因为一次测试无法有效的代表结果,// 所以通过3轮测试较为全面的测试,// 而每一轮都是先预热,再正式计量。.forks(3).output("E:/Benchmark.log").build();new Runner(opt).run();}

结语

基于JMH可以对很多工具和框架进行测试,比如日志框架性能对比、BeanCopy性能对比 等,更多的example可以参考官方给出的JMH samples (https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/)

作者从 Java Developer 角度来谈谈一些常见的代码测试陷阱,分析他们和操作系统底层以及 Java 底层的关联性,并借助 JMH 来帮助大家摆脱这些陷阱。

作者:Richard_Yi
来源:juejin.cn/post/6844903936869007368

别再写 main 方法测试了,太 Low,这才是专业 Java 测试方法相关推荐

  1. 别再写 main 方法测试了,太 Low!这才是专业 Java 测试方法!

    欢迎关注方志朋的博客,回复"666"获面试宝典 作者:Richard_Yi 来源:juejin.cn/post/6844903936869007368 前言 "If yo ...

  2. 不要再用main方法测试代码性能了,用这款JDK自带工具

    前言 作为软件开发人员,我们通常会写一些测试程序用来对比不同算法.不同工具的性能问题.而最常见的做法是写一个main方法,构造模拟场景进行并发测试. 如果细心的朋友可能已经发现,每次测试结果误差很大, ...

  3. JUnit:求求你了,别再用 main 方法测试了,好吗?

    01.前世今生 你好呀,我是 JUnit,一个开源的 Java 单元测试框架.在了解我之前,先来了解一下什么是单元测试.单元测试,就是针对最小的功能单元编写测试代码.在 Java 中,最小的功能单元就 ...

  4. 通过代码创建一个文件a.txt 然后写一个方法,控制台循环输入内容,然后通过gbk的编码格式保存到 a.txt中(要求可以追加),直到输入exit结束 再写一个方法,要求用字符缓冲流读取a.tx

    通过代码创建一个文件a.txt 然后写一个方法,控制台循环输入内容,然后通过gbk的编码格式保存到 a.txt中(要求可以追加),直到输入exit结束 再写一个方法,要求用字符缓冲流读取a.txt中的 ...

  5. 微信v3支付签名 java main方法测试成功

  6. JUnit的优点在于什么地方?和main方法有什么不同?

    JUnit的好处在于. 可以对代码进行单元测试. 并且可以根据单元测试生成单元测试报告. 比代码中写main 方法测试的好处: 可以书写一系列的 测试方法,对项目所有的 接口或者方法进行单元测试. 2 ...

  7. java中main方法前的public static void及其后面的(String[] args)【笔记自用】

    为什么Java的main方法必须是public static void? 一. void 如下,像C, C++一样,将返回值类型改为int,再返回一个0,虽然编译通过,但是运行时会报错. 找到一种可理 ...

  8. java main 参数传递参数_Java千问:Java语言如何给main方法传递参数?

    大家都知道,Java语言运行程序的入口方法叫做main,这个方法有一个参数,这就表示说,我们要运行主方法,就必须给main方法传递一个参数.但是,大家似乎对这个参数向来都是视而不见,从来都没给主方法传 ...

  9. 深入理解Java的main方法

    一.简单介绍 在我们的Java程序中都会出现一个名称为main的方法,我们发现没有这个方法我们的程序就无法运行. 其实任何一个Java程序的运行入口都是这个main方法,也就是说,程序在运行的时候,第 ...

  10. idea执行main方法都要BUILD解决

    开发代码中,很多时候我们为了验证一小段代码逻辑会直接写个main方法去执行,看看运行结果,最近发现idea 跑个main方法都要执行 compileJava 和 BUILD 解决方案: 去掉Deleg ...

最新文章

  1. java精确浮点型小数,java练习 计算n位可被浮点数精确表示的小数
  2. 【短视频SDK】Android如何使用硬编硬解?
  3. gsoap写一个c++ webservice
  4. AndroidAnnotations开发框架在Eclipse中的搭建和使用以及框架实现的原理
  5. SpringMVC自定义注入controller变量
  6. java的反射和它的类加载机制
  7. 实验四android开发基础
  8. 程序员,岂能被网站吞吐量难住?
  9. mysql sam和db_Mysql
  10. 私塾在线《研磨设计模式》,精品课程上线特大惊喜
  11. oracle exadata维保,EXADATA数据一体机巡检说明
  12. webbrowser点击网页内部链接阻止从IE打开
  13. 中兴承建WoStore:联通摒弃苹果“自封神话”?
  14. 基于单片机的led阅读灯方案
  15. 5A学友的备考心得 | PMP考试如何一把过?
  16. 每日好店——淘宝店铺推荐系统实践
  17. pandas输出excel文件添加表头标题,样式装饰器
  18. thinkphp6学习教程与源码 tp6开源CMS系统源码研究
  19. php单链表检测有没有环,写一段代码判断单向链表中有没有形成环,如果形成环,请找出环的入口处,即P点...
  20. MySQL [1093] You can‘t specify target table ‘titles_test‘ for update in FROM clause

热门文章

  1. Node.js:Node模块简介
  2. Pannellum:实例之通过全景图游览
  3. 正则表达式及常用大全
  4. System.IndexOutOfRangeException: 无法找到表 0解决办法
  5. C/C++调用python,opencv+python
  6. LeetCode之移除元素
  7. (转)使用CUnit进行单元测试和覆盖率统计
  8. 【Python3爬虫】网易云音乐歌单下载
  9. 谈谈一些有趣的CSS题目(十一)-- reset.css 知多少?
  10. python 日志收集系统