使用JMH进行基准测试

什么是JMH

Java Microbenchmark Harness, 由JIT开发人员开发的,于2013年发布的一款基于Java的微基准测试工具,现已归于JDK。

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

JMH主要用于量化Java代码的性能,主要应用场景如下:

  • 对于已知的热点函数进行进一步优化

  • 确认函数执行时间以及于参数的关系

  • 比较相同功能的函数的性能

使用JMH

  1. 添加JMH依赖

 <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-core</artifactId>
	<version>1.21</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.21</version>
	<scope>test</scope>
</dependency>
  1. 安装JMH插件,同Junit类似,测试方法的运行要借助于插件才可运行:分别在IDEA于Eclipse插件商店中搜索插件JMH

  2. 开启IDEA注解处理: compiler -> Annotation Processors -> Enable Annotation Processing

  3. 定义测试方法,打上注解 @Benchmark标记方法为测试方法,并使用IDEA运行

  4. 运行结果即为测试报告


注意:

在运行时可能发生如下异常:

此异常是因为JMH运行需要访问系统的TMP目录,TMP目录定义在环境变量上,然后在此路径下创建lock文件,由于我们未定义TMP环境变量,它默认会去C:\WINDOWS\ 路径下创建,此目录要求管理员权限,所以需要修改lock文件的路径;操作系统默认定义了一个临时目录的系统环境变量,我们也可以将系统环境变量加入到我们的启动配置中:


关于错误Unable to find the resource: /META-INF/BenchmarkList ...,出现这种错误有可能有如下几种情况:

  • 只添加了org.openjdk.jmh:jmh-core,缺少org.openjdk.jmh:jmh-generator-annprocess依赖

  • 测试类没有放在src/test路径下,而是放在了src/main

范例

分别运行 testForEachtestParallel,会得到如下两个测试报告:

普通foreach获取质数的性能报告:

1.8 Parallel 遍历获取质数的测试报告:

(这里由于篇幅的原因,只截取部分)

可以看到 parallel 的吞吐量明显大于普通for循环的吞吐量。

JMH 基本概念

  • Warmup 预热,由于JVM中对于特定代码会存在优化(本地化),预热对于测试结果很重要

  • Mesurement 总共执行多少次测试

  • Benchmark mode 基准测试的模式

@BenchmarkMode

基准测试类型:

  • Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。

  • AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。

  • SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”

  • SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。

  • All(“all”, “All benchmark modes”);

@Warmup

一般我们前几次进行程序测试的时候都会比较慢, 所以要让程序进行几轮预热,保证测试的准确性。iterations就是预热轮数。time则是每次预热时间,batchSize:批处理大小,每次操作调用几次方法。

@Measurement

度量,其实就是一些基本的测试参数。

  • iterations 进行测试的轮次

  • time 每轮进行的时长

  • timeUnit 时长单位

都是一些基本的参数,可以根据具体情况调整。一般比较重的东西可以进行大量的测试,放到服务器上运行。

@Threads

每个进程中的测试线程,可用于类或者方法上。一般选择为cpu乘以2。如果配置了 Threads.MAX ,代表使用 Runtime.getRuntime().availableProcessors() 个线程。

@Fork

进行 fork 的次数。可用于类或者方法上。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。

@OutputTimeUnit

这个比较简单了,基准测试结果的时间类型。一般选择秒、毫秒、微秒。

@Benchmark

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

@Param

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

@Setup

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

@TearDown

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

value值:

  • Trial:在每次Benchmark的之前/之后执行。

  • Iteration:在每次Benchmark的iteration的之前/之后执行。

  • Invocation:每次调用Benchmark标记的方法之前/之后都会执行。

@State

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

参考

最后更新于

这有帮助吗?