关注Java后端编程,推送更多 Java 干货!
来源:https://juejin.cn/post/6844903936869007368
前言
“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进行说明。另外,最新最全的 Java 面试题整理好了,微信搜索Java面试库小程序在线刷题。
为了方便,这里直接介绍使用maven构建JMH测试项目的方式。
第一种是使用命令行构建,在指定目录下执行以下命令:
$mvnarchetype: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。23 种设计模式实战(很全)分享给你。
org.openjdk.jmh
jmh-core
${jmh.version}
org.openjdk.jmh
jmh-generator-annprocess
${jmh.version}
provided
2. 编写性能测试
这里我以测试LinkedList 通过index 方式迭代和foreach 方式迭代的性能差距为例子,编写测试类,涉及到的注解在之后会讲解,最新面试题整理好了,点击Java面试库小程序在线刷题。
/**
*@authorRichard_yyf
*@version1.02019/8/27
*/
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(Threads.MAX)
publicclassLinkedListIterationBenchMark{
privatestaticfinalintSIZE=10000;
privateListlist=newLinkedList<>();
@Setup
publicvoidsetUp(){
for(inti=0;i3. 执行测试
运行JMH基准测试有两种方式,一个是生产jar文件运行,另一个是直接写main函数或者放在单元测试中执行。最新面试题整理好了,点击Java面试库小程序在线刷题。
生成jar文件的形式主要是针对一些比较大的测试,可能对机器性能或者真实环境模拟有一些需求,需要将测试方法写好了放在linux环境执行。
具体命令如下
$mvncleaninstall
$java-jartarget/benchmarks.jar
我们日常中遇到的一般是一些小测试,比如我上面写的例子,直接在IDE中跑就好了。
启动方式如下:
publicstaticvoidmain(String[]args)throwsRunnerException{
Optionsopt=newOptionsBuilder()
.include(LinkedListIterationBenchMark.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(2)
.output("E:/Benchmark.log")
.build();
newRunner(opt).run();
}
4. 报告结果
输出结果如下,
最后的结果:
BenchmarkModeCntScoreErrorUnits
LinkedListIterationBenchMark.forEachIteratethrpt21192.380ops/s
LinkedListIterationBenchMark.forIndexIteratethrpt2206.866ops/s
整个过程:
DetectingactualCPUcount:12detected
JMHversion:1.21
VMversion:JDK1.8.0_131,JavaHotSpot(TM)64-BitServerVM,25.131-b11
VMinvoker:C:\ProgramFiles\Java\jdk1.8.0_131\jre\bin\java.exe
VMoptions:-javaagent:D:\ProgramFiles\JetBrains\IntelliJIDEA2018.2.2\lib\idea_rt.jar=65175:D:\ProgramFiles\JetBrains\IntelliJIDEA2018.2.2\bin-Dfile.encoding=UTF-8
Warmup:2iterations,10seach
Measurement:2iterations,10seach
Timeout:10minperiteration
Threads:12threads,willsynchronizeiterations
Benchmarkmode:Throughput,ops/time
Benchmark:org.sample.jmh.LinkedListIterationBenchMark.forEachIterate
Runprogress:0.00%complete,ETA00:01:20
Fork:1of1
WarmupIteration1:1189.267ops/s
WarmupIteration2:1197.321ops/s
Iteration1:1193.062ops/s
Iteration2:1191.698ops/s
Result"org.sample.jmh.LinkedListIterationBenchMark.forEachIterate":
1192.380ops/s
JMHversion:1.21
VMversion:JDK1.8.0_131,JavaHotSpot(TM)64-BitServerVM,25.131-b11
VMinvoker:C:\ProgramFiles\Java\jdk1.8.0_131\jre\bin\java.exe
VMoptions:-javaagent:D:\ProgramFiles\JetBrains\IntelliJIDEA2018.2.2\lib\idea_rt.jar=65175:D:\ProgramFiles\JetBrains\IntelliJIDEA2018.2.2\bin-Dfile.encoding=UTF-8
Warmup:2iterations,10seach
Measurement:2iterations,10seach
Timeout:10minperiteration
Threads:12threads,willsynchronizeiterations
Benchmarkmode:Throughput,ops/time
Benchmark:org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate
Runprogress:50.00%complete,ETA00:00:40
Fork:1of1
WarmupIteration1:205.676ops/s
WarmupIteration2:206.512ops/s
Iteration1:206.542ops/s
Iteration2:207.189ops/s
Result"org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate":
206.866ops/s
Runcomplete.Totaltime:00:01:21
REMEMBER:Thenumbersbelowarejustdata.Togainreusableinsights,youneedtofollowupon
whythenumbersarethewaytheyare.Useprofilers(see-prof,-lprof),designfactorial
experiments,performbaselineandnegativeteststhatprovideexperimentalcontrol,makesure
thebenchmarkingenvironmentissafeonJVM/OS/HWlevel,askforreviewsfromthedomainexperts.
Donotassumethenumberstellyouwhatyouwantthemtotell.
BenchmarkModeCntScoreErrorUnits
LinkedListIterationBenchMark.forEachIteratethrpt21192.380ops/s
LinkedListIterationBenchMark.forIndexIteratethrpt2206.866ops/s
注解介绍
下面我们来详细介绍一下相关的注解。关注Java项目精选,推送更多 Java 干货!
@BenchmarkMode
微基准测试类型。JMH提供了以下几种类型进行支持:
可以注释在方法级别,也可以注释在类级别。
@BenchmarkMode(Mode.All)
publicclassLinkedListIterationBenchMark{
...
}
@Benchmark
@BenchmarkMode({Mode.Throughput,Mode.SingleShotTime})
publicvoidm(){
...
}
@Warmup
这个单词的意思就是预热,iterations = 3
就是指预热轮数。
@Benchmark
@BenchmarkMode({Mode.Throughput,Mode.SingleShotTime})
@Warmup(iterations=3)
publicvoidm(){
...
}
@Measurement
正式度量计算的轮数。
iterations
进行测试的轮次
time
每轮进行的时长
timeUnit
时长单位
@Benchmark
@BenchmarkMode({Mode.Throughput,Mode.SingleShotTime})
@Measurement(iterations=3)
publicvoidm(){
...
}
@Threads
每个进程中的测试线程。
@Threads(Threads.MAX)
publicclassLinkedListIterationBenchMark{
...
}
@Fork
进行 fork 的次数。如果 fork 数是3的话,则 JMH 会 fork 出3个进程来进行测试。
@Benchmark
@BenchmarkMode({Mode.Throughput,Mode.SingleShotTime})
@Fork(value=3)
publicvoidm(){
...
}
@OutputTimeUnit
基准测试结果的时间类型。一般选择秒、毫秒、微秒。
@OutputTimeUnit(TimeUnit.SECONDS)
publicclassLinkedListIterationBenchMark{
...
}
@Benchmark
方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的@Test
类似。分享资料:23 种设计模式实战(很全)
@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两个方法来完成包含以及排除的语义
*/
publicstaticvoidmain(String[]args)throwsRunnerException{
Optionsopt=newOptionsBuilder()
//包含语义
//可以用方法名,也可以用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();
newRunner(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/)
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。
关注公众号:Java后端编程,回复下面关键字
要Java学习完整路线,回复路线缺Java入门视频,回复:视频要Java面试经验,回复面试缺Java项目,回复:项目进Java粉丝群:加群
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。(完)
加我"微信"获取一份 最新Java面试题资料请备注:666,不然不通过~
最近好文
1、Kafka 3.0重磅发布,弃用 Java 8 的支持!2、你只会用 ! = null 判空?嘿嘿!3、这次,Swagger-ui遇到对手了!4、一个基于Spring Boot+Vue+Redis的物联网智能家居系统5、本机号码一键登录原理与应用
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:关注公众号并回复java领取,更多内容陆续奉上。明天见(。・ω・。)
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容