使用JMH进行基准测试
什么是JMH
Java Microbenchmark Harness, 由JIT开发人员开发的,于2013年发布的一款基于Java的微基准测试工具,现已归于JDK。
官网: http://openjdk.java.net/projects/code-tools/jmh/
JMH主要用于量化Java代码的性能,主要应用场景如下:
对于已知的热点函数进行进一步优化
确认函数执行时间以及于参数的关系
比较相同功能的函数的性能
使用JMH
添加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>安装JMH插件,同Junit类似,测试方法的运行要借助于插件才可运行:分别在IDEA于Eclipse插件商店中搜索插件JMH
开启IDEA注解处理:
compiler -> Annotation Processors -> Enable Annotation Processing定义测试方法,打上注解
@Benchmark标记方法为测试方法,并使用IDEA运行运行结果即为测试报告
注意:
在运行时可能发生如下异常:
此异常是因为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下
范例
分别运行 testForEach 和 testParallel,会得到如下两个测试报告:
普通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参数的时候,必须在类上加这个参数,不然会提示无法运行
参考
最后更新于
这有帮助吗?