0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

JMH的DEMO演示和常用的注解参数

我快闭嘴 来源:稀土掘金技术社区 作者:Richard_Yi 2022-09-27 10:49 次阅读

3343897c-3e0b-11ed-9e49-dac502259ad0.jpg

前言

"If you cannot measure it, you cannot improve it".

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

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

3356404e-3e0b-11ed-9e49-dac502259ad0.jpg

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

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

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

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

JMH的使用场景:

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

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

DEMO 演示

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

1. 测试项目构建

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

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

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

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

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

33670816-3e0b-11ed-9e49-dac502259ad0.jpg

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

<dependency>
<groupId>org.openjdk.jmhgroupId>
<artifactId>jmh-coreartifactId>
<version>${jmh.version}version>
dependency>
<dependency>
<groupId>org.openjdk.jmhgroupId>
<artifactId>jmh-generator-annprocessartifactId>
<version>${jmh.version}version>
<scope>providedscope>
dependency>

2. 编写性能测试

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

/**
*@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;i< SIZE; i++) {
            list.add(String.valueOf(i));
        }
    }

    @Benchmark
@BenchmarkMode(Mode.Throughput)
publicvoidforIndexIterate(){
for(inti=0;i< list.size(); i++) {
            list.get(i);
            System.out.print("");
}
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
publicvoidforEachIterate(){
for(Strings:list){
System.out.print("");
}
}
}

3. 执行测试

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

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

整个过程:

>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element实现的后台管理系统+用户小程序,支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
>*项目地址:
>*视频教程

#DetectingactualCPUcount:12detected
#JMHversion:1.21
#VMversion:JDK1.8.0_131,JavaHotSpot(TM)64-BitServerVM,25.131-b11
#VMinvoker:C:ProgramFilesJavajdk1.8.0_131jreinjava.exe
#VMoptions:-javaagentProgramFilesJetBrainsIntelliJIDEA2018.2.2libidea_rt.jar=65175ProgramFilesJetBrainsIntelliJIDEA2018.2.2in-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,ETA0020
#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:ProgramFilesJavajdk1.8.0_131jreinjava.exe
#VMoptions:-javaagentProgramFilesJetBrainsIntelliJIDEA2018.2.2libidea_rt.jar=65175ProgramFilesJetBrainsIntelliJIDEA2018.2.2in-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,ETA0040
#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:0021

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

注解介绍

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

@BenchmarkMode

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

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

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

@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 类似。

@Param

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

@Setup

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

@TearDown

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

@State

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

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

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

  1. Thread: 该状态为每个线程独享。
  2. Group: 该状态为同一个组里面所有线程共享。
  3. 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/)



审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2943

    浏览量

    104110
  • JVM
    JVM
    +关注

    关注

    0

    文章

    155

    浏览量

    12168

原文标题:别再写 main 方法测试了,太 Low!这才是专业 Java 测试方法!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何通过注解来优化我们的Java代码

    Java注解可以说是我们编码过程中最常用的。本篇文章将给大家介绍Java注解的概念、作用以及如何使用注解来提升代码的可读性和灵活性,并介绍如何通过
    的头像 发表于 09-30 11:39 513次阅读

    【HarmonyOS IPC 试用连载 】开箱+DEMO演示

    钉,然后再装。 注意:镙钉先别扭紧,调好焦后再扭紧。组装搞定,可以开始搞机了!3.demo演示 1)出厂程序DEMO演示依据说明文档。扫描二维码下载手机APP。安装APP(P6SLit
    发表于 10-23 00:40

    HarmonyOS注解的使用方法分享

    ; }}运行项目报错:缺少get方法! 类名:com.example.annotation.demo.Person 参数名:age
    发表于 03-28 14:04

    PWM_Demo:F280x PWM 功能演示应用程序

    PWM_Demo:F280x PWM 功能演示应用程序
    发表于 04-10 14:33 8次下载

    基于TX4101制作的DEMO演示

    基本描述: 本 DEMO 为 TX4101 制作的演示板,用于 DC 输入4.2-5.0V,输出电压 3.3V,输出电流 600mA 的应用演示,最高转换效率高达 93%。 本电路固定工作频率是:1MHz。输出 3.3V/600
    发表于 05-07 14:58 430次阅读
    基于TX4101制作的<b class='flag-5'>DEMO</b><b class='flag-5'>演示</b>板

    基于TX4205制作的DEMO演示

    DEMO 为 TX4205 制作的演示板,用于 DC 输入 3.6-5.0V,输出恒压12V,输出电流 1A 的应用演示,最高转换效率高达 87%。
    发表于 05-11 11:10 586次阅读
    基于TX4205制作的<b class='flag-5'>DEMO</b><b class='flag-5'>演示</b>板

    基于TX6410制作的DEMO演示

    DEMO 为 TX6410 制作的演示板,用于 DC 输入 12-24V,多种电压输出, 输出电流 350mA 的应用演示,最高转换效率高达 88%。 本电路实现了线性降压恒流功能
    的头像 发表于 05-11 11:36 1429次阅读
    基于TX6410制作的<b class='flag-5'>DEMO</b><b class='flag-5'>演示</b>板

    Spring Boot常用注解与使用方式

    企业开发项目SpringBoot已经是必备框架了,其中注解是开发中的小工具(谁处可见哦),用好了开发效率大大提升,当然用错了也会引入缺陷。
    的头像 发表于 07-08 10:57 1261次阅读

    SpringBoot常用注解及使用方法1

    基于 SpringBoot 平台开发的项目数不胜数,与常规的基于`Spring`开发的项目最大的不同之处,SpringBoot 里面提供了大量的注解用于快速开发,而且非常简单,基本可以做到开箱即用! 那 SpringBoot 为开发者提供了多少注解呢?我们该如何使用
    的头像 发表于 04-07 11:51 592次阅读

    SpringBoot常用注解及使用方法2

    基于 SpringBoot 平台开发的项目数不胜数,与常规的基于Spring开发的项目最大的不同之处,SpringBoot 里面提供了大量的注解用于快速开发,而且非常简单,基本可以做到开箱即用!
    的头像 发表于 04-07 11:52 557次阅读

    Springboot常用注解合集

    前几章,在系统启动类里面,都加入了此启动注解,此注解是个组合注解,包括了`@SpringBootConfiguration`、`@EnableAutoConfiguration`和`@ComponentScan`
    的头像 发表于 04-07 14:27 651次阅读
    Springboot<b class='flag-5'>常用</b><b class='flag-5'>注解</b>合集

    SpringBoot常用注解及原理

    SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类, 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
    的头像 发表于 04-07 14:30 511次阅读

    JAVA中注解是怎么做到的(上)

    注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:`@Controller`、`@Service`、`@RequestMapping`等等,它是JDK1.5及以后版本引入的一个特性
    的头像 发表于 05-11 10:57 576次阅读

    JAVA中注解是怎么做到的(下)

    注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:`@Controller`、`@Service`、`@RequestMapping`等等,它是JDK1.5及以后版本引入的一个特性
    的头像 发表于 05-11 10:57 484次阅读
    JAVA中<b class='flag-5'>注解</b>是怎么做到的(下)

    springmvc常用5种注解

    SpringMVC是一种基于Java的Web框架,使用注解可以更加方便灵活地开发和管理控制器,实现请求的映射和处理。在SpringMVC中,有许多常用注解,本文将详细介绍其中的五种注解
    的头像 发表于 11-22 16:51 647次阅读