聊聊springboot项目如何利用jmh来进行基准测试

news/2024/11/19 12:39:58/文章来源:https://www.cnblogs.com/linyb-geek/p/18041030

前言

1、什么是JMH

JMH(Java Microbenchmark Harness)是由OpenJDK团队开发的一个用于Java微基准测试工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。它提供了一种标准、可靠且可重复的方式来衡量Java代码的性能,包括方法调用、对象创建以及其他类型的 JVM 级别的操作。JMH 通过生成优化过的字节码来确保基准测试不受常见陷阱的影响,如热身不足、垃圾回收干扰、编译器优化等,从而产生更准确的性能指标

2、JMH主要使用场景

  • 精确测量方法执行时间: 当你需要准确知道某个特定Java方法或代码段在不同输入、不同环境条件下的执行时间时,可以使用JMH进行基准测试。例如,你可能想比较不同字符串连接方法(如String.concat()与StringBuilder.append())的性能差异。
  • 吞吐量对比: 在评估接口实现或者算法效率时,JMH可以帮助你对比不同实现在相同工作负载下的吞吐量,即单位时间内能够处理的任务数量。
  • 响应时间和分布分析: JMH不仅提供平均执行时间的数据,还可以帮助分析请求完成的时间分布情况,比如你可以了解到多少百分比的请求能在多长时间内完成。
  • 性能优化验证: 在对代码进行性能优化后,使用JMH进行基准测试可以量化改进前后的性能差异,确保优化措施确实提高了程序的运行效率。
  • 并发和并行性能评估: 对于涉及多线程和并发操作的代码块,JMH提供了强大的工具来测量在不同并发级别下系统的性能表现。
  • JVM行为研究: 由于JMH深入到JVM层面进行测试,并且能控制垃圾收集、编译器优化等因素的影响,它对于理解JVM如何影响代码性能以及研究内存分配、垃圾回收策略等具有重要意义。
  • 跨平台可比性: 使用JMH可以在不同的Java版本、不同的操作系统和硬件配置上得到相对可比的基准测试结果,有助于在多种环境下评估代码性能的一致性。

3、JMH常用注解

注: 因为我们主要利用JMH提供的注解来进行基准测试,因此我们有必要了解一下JMH一些常用注解

@State: 表明类的所有属性的作用域。只能用于类上。它有如下选项

  • Scope.Thread: 默认的State,每个测试线程分配一个实例;
  • Scope.Benchmark: 所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
  • Scope.Group: 每个线程组共享一个实例;

@BenchmarkMode: 用于指定基准测试的执行模式,如吞吐量、平均执行时间。可用于类或者方法上,它有如下模式

  • Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
  • AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
  • SampleTime:随机取样,最后输出取样结果的分布
  • SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
  • All:上面的所有模式都执行一次

@Measurement: 用于控制压测的次数、时间和批处理数量。可用于类或者方法上,它有如下参数

  • iterations:测量的次数
  • time:每次测量持续的时间
  • timeUnit:时间的单位,默认秒
  • batchSize:批处理大小,每次操作调用几次方法

@Warmup: 预热,可用于类或者方法上

由于JVM会使用JIT对热点代码进行编译,因此同一份代码可能由于执行次数的增加而导致执行时间差异太大,因此我们可以让代码先预热几轮,预热时间不算入测量计时。@WarmUp 的使用和 @Measurement 一致。

@Fork: 用于指定fork出多少个子进程来执行同一基准测试方法,可用于类或者方法上。例如@Fork指定数量为2,则 JMH 会 fork 出两个进程来进行测试

@Threads: 用于指定使用多少个线程来执行基准测试方法,可用于类或者方法上。例如@Threads 指定线程数为 2 ,那么每次测量都会创建两个线程来执行基准测试方法

@OutputTimeUnit: 可以指定输出的时间单位,可用于类或者方法注解

@Param: 指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义 @State 注解。

@Setup: 用于基准测试前的初始化动作,只能用于方法

@TearDown 用于基准测试后执行,主要用于资源的回收,只能用于方法

4、JMH陷阱

常见的比如死码消除。所谓的死码,是指注释的代码,不可达的代码块,可达但不被使用的代码等等。如示例下例子

 @Benchmarkpublic void testMethod() {int a = 1;int b = 2;int sum = a + b;}

JVM可以检测到分配给sum的a+b的计算从未被使用。因此,JVM可以完全取消a+b的计算。它被认为是死代码。JVM然后可以检测到sum变量从未被使用,并且随后a和b也从未被使用。他们也可以被淘汰。上面的例子最终会被优化成

 @Benchmarkpublic void testMethod() {}

这样会影响测试结果。JMH提供了如下两种方法来避免死码。一种是将变量当成返回值返回。示例

 @Benchmarkpublic int testMethod() {int a = 1;int b = 2;int sum = a + b;return sum;}

一种是利用Blackhole 的 consume 来避免 JIT 的优化消除。
示例:

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;public class MyBenchmark {@Benchmarkpublic void testMethod(Blackhole blackhole) {int a = 1;int b = 2;int sum = a + b;blackhole.consume(sum);}
}

其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用 Fork 隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的朋友可以阅读
https://github.com/lexburner/JMH-samples
了解全部的陷阱。

正文

通过前面的铺垫,大家对jmh应该有个大致的了解,接下来我们就来演示一下springboot项目如何利用jmh进行基准测试

1、springboot的项目中引入JMH GAV

 <properties><jmh.version>1.36</jmh.version></properties><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、编写测试

注: 因为有前面的铺垫介绍,因此下面的例子大家应该比较容易看得懂,就不再论述,直接上代码

@Measurement(iterations = 2, time = 10)
@Warmup(iterations = 2, time = 10)
@Fork(1)
@Threads(value = 2)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.SECONDS)
public class SpringBootJmhTest {private ConfigurableApplicationContext context;private MockBizService mockBizService;/*** @Param 允许使用一份基准测试代码跑多组数据,特别适合测量方法性能和参数取值的关系*/@Param({"100","500","1"})public long mockBizQueryTime;/***   注意要使用 run模式启动main函数,不要使用debug模式启动。*  否则会报错:transport error 202: connect failed: Connection refused ERROR* @param args* @throws RunnerException*/public static void main(String[] args) throws RunnerException {String report = DateUtil.today() + "-jmhReport.json";Options opt = new OptionsBuilder().include(SpringBootJmhTest.class.getSimpleName())// 参数优先级顺序:类 < 方法 < Options// 因此如下配置会覆盖@Warmup配置.warmupIterations(1).warmupTime(TimeValue.seconds(5))//报告输出.可以将结果上传到 https://jmh.morethan.io 或者/http://deepoove.com/jmh-visual// 进行分析.result(report)//报告格式.resultFormat(ResultFormatType.JSON).build();new Runner(opt).run();}/*** @Setup 用于基准测试前的初始化动作** Level参数表明粒度,粒度从粗到细分别是** Level.Trial:Benchmark级别* Level.Iteration:执行迭代级别* Level.Invocation:每次方法调用级别*/@Setup(Level.Trial)public void setUp(){context = SpringApplication.run(SpringBootJmhApplication.class);mockBizService = context.getBean(MockBizService.class);}/**** @Benchmark 来标记需要基准测试的方法.该方法需要为public* @param blackhole 的作用是:防止无用代码被JVM优化导致的基准测试结果不准确*/@Benchmarkpublic void testMockBizService(Blackhole blackhole) {blackhole.consume(mockBizService.query(mockBizQueryTime));}/*** @TearDown 用于基准测试后执行*/@TearDownpublic void tearDown() {context.close();}}

3、运行JMH

运行的方式常见有如下几种,一种是直接运行main函数

如示例

 /***   注意要使用 run模式启动main函数,不要使用debug模式启动。*  否则会报错:transport error 202: connect failed: Connection refused ERROR* @param args* @throws RunnerException*/public static void main(String[] args) throws RunnerException {String report = DateUtil.today() + "-jmhReport.json";Options opt = new OptionsBuilder().include(SpringBootJmhTest.class.getSimpleName())// 参数优先级顺序:类 < 方法 < Options// 因此如下配置会覆盖@Warmup配置.warmupIterations(1).warmupTime(TimeValue.seconds(5))//报告输出.可以将结果上传到 https://jmh.morethan.io 或者/http://deepoove.com/jmh-visual// 进行分析.result(report)//报告格式.resultFormat(ResultFormatType.JSON).build();new Runner(opt).run();}

执行main方法,记得需要使用run模式运行,如图

而不是以debug模式,否则会报

transport error 202: connect failed: Connection refused ERROR

另一种是IDE安装JMH插件,以idea为例,在plugins搜索JMH,然后安装插件,安装成功后,可以像执行单元测试那种,单独运行加@Benchmark注解的方法
示例

可以点击圈红的小图标运行,也可以选中加了@Benchmark的方法,右键run运行


还有一种是直接打成jar运行

打成jar包也有如下两种方式。一种在项目的pom引入相应的打包插件

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><finalName>springboot-jmh</finalName><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.handlers</resource></transformer><transformerimplementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"><resource>META-INF/spring.factories</resource></transformer><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.schemas</resource></transformer><transformerimplementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.github.lybgeek.jmh.SpringBootJmhTest</mainClass></transformer></transformers></configuration></execution></executions></plugin></plugins>
</build>

这种插件的注意点是main函数直接指定我们要进行基准测试的main函数的类,比如

com.github.lybgeek.jmh.SpringBootJmhTest

其次因为我们springboot运行会依赖一些自动装配,因此我们也需要将相关的配置比如spring.factories装载进去。不然打包的时候可能会报

Cannot find 'resource' in class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer

不过这只是其中一种解法,下边我后讲解另一种解法。

运行如下命令

mvn clean package
java -jar springboot-jmh.jar  -rf json -rff D:/jmhResult.json

其中-rf: 为输出的格式为json -rff: 为指定输出的位置

另外一种直接引入官方提供示例插件,该插件也是shade插件,只是此时mainclass为

org.openjdk.jmh.Main
  <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.1</version><executions><execution><id>shade-my-jar</id><phase>package</phase><goals><goal>shade</goal></goals><configuration><finalName>springboot-jmh</finalName><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>org.openjdk.jmh.Main</mainClass></transformer></transformers></configuration></execution></executions></plugin></plugins>

这边有个小细节,是因为springboot本身也有依赖shade插件,因此我们自己的shade插件要指定id。如示例配置

 <plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><executions><execution><id>shade-my-jar</id>
...

否则会和springboot默认的插件id冲突,而导致出现

Cannot find 'resource' in class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer

配置完成后打包,并运行如下命令

java -jar springboot-jmh.jar SpringBootJmhTest  -rf json -rff D:/jmhResult.json

注: SpringBootJmhTest 为我们要进行JMH测试的类

以上几种执行方式如何取舍

如果是小测试,直接通过main函数或者jmh插件运行即可。如果是比较大的测试,测试时间比较长,且需要可能需要比较多的资源,可以打成jar测试

4、查看测试结果

Benchmark                                                        (mockBizQueryTime)    Mode  Cnt   Score    Error  Units
SpringBootJmhTest.testMockBizService                                            100   thrpt    2  18.554           ops/s
SpringBootJmhTest.testMockBizService                                            500   thrpt    2   3.935           ops/s
SpringBootJmhTest.testMockBizService                                              1   thrpt    2   1.986           ops/s
SpringBootJmhTest.testMockBizService                                            100    avgt    2   0.108            s/op
SpringBootJmhTest.testMockBizService                                            500    avgt    2   0.509            s/op
SpringBootJmhTest.testMockBizService                                              1    avgt    2   1.005            s/op
SpringBootJmhTest.testMockBizService                                            100  sample  370   0.108 ±  0.001   s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.00                   100  sample        0.103            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.50                   100  sample        0.108            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.90                   100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.95                   100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.99                   100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.999                  100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.9999                 100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p1.00                   100  sample        0.109            s/op
SpringBootJmhTest.testMockBizService                                            500  sample   78   0.507 ±  0.001   s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.00                   500  sample        0.500            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.50                   500  sample        0.508            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.90                   500  sample        0.510            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.95                   500  sample        0.511            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.99                   500  sample        0.513            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.999                  500  sample        0.513            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.9999                 500  sample        0.513            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p1.00                   500  sample        0.513            s/op
SpringBootJmhTest.testMockBizService                                              1  sample   38   1.005 ±  0.003   s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.00                     1  sample        0.999            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.50                     1  sample        1.002            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.90                     1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.95                     1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.99                     1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.999                    1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p0.9999                   1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService:testMockBizService·p1.00                     1  sample        1.014            s/op
SpringBootJmhTest.testMockBizService                                            100      ss    2   0.110            s/op
SpringBootJmhTest.testMockBizService                                            500      ss    2   0.508            s/op
SpringBootJmhTest.testMockBizService                                              1      ss    2   1.010            s/op

报告的参数解读如下

Mode: 模式

  • thrpt:吞吐量
  • avgt:每次请求的平均耗时
  • sample:请求样本数量,这次压测一共发了多少个请求
  • ss:除去冷启动,一共执行了多少轮

Cnt: 基准测试执行的迭代次数或者样本数量

Score: 是性能测试结果的主要度量单位。它代表了基准测试方法的吞吐量或者执行速度,具体含义取决于你选择的@BenchmarkMode。

例如你设置了 @BenchmarkMode(Mode.Throughput),那么 Score 将表示每秒可以执行该操作的次数(ops/s),即吞吐量。 - 若设置为 @BenchmarkMode(Mode.AverageTime),则 Score 表示的是平均每个操作所需的时间(如ns/op、ms/op等),数值越小通常意味着性能越好

Errors: 通常指的是执行过程中统计性能指标时的误差范围。由于JMH基于统计学原理进行性能测量,因此其结果会受到随机性和系统噪声的影响

Units: 通常指的是度量基准测试结果时使用的单位。根据你选择的@BenchmarkMode不同,报告中的单位也会有所变化

5、jmh测试结果可视化

我们可以将生成jmh的json结果上传到如下网站,进行可视化分析

  • JMH Visual Chart:hhttp://deepoove.com/jmh-visual-chart/
  • Visualizer:https://jmh.morethan.io/

总结

本文主要大致讲下如何使用jmh。jmh的详细案例,可以查看官网
https://github.com/openjdk/jmh
或者查看下面博主写的文章
https://cloud.tencent.com/developer/article/1760933

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-jmh

参考文档

https://jenkov.com/tutorials/java-performance/jmh.html
https://www.cnblogs.com/wupeixuan/p/13091381.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/741034.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

echart - x轴文字太长换行、文字倾斜、文字竖直展示

echart - x轴文字太长换行、文字倾斜、文字竖直展示 设置超过几个字换行显示xAxis: {axisLabel: {formatter: function (params) {var str = ""; // 最终拼接成的字符串var paramsLen = params.length;// 获取每项文字的个数var len = 4; …

Vscode+CodeRunner 更加优雅的运行MPICC

Vscode+CodeRunner 更加优雅的运行MPICC 1.安装 在VsCode拓展中安装CodeRunner2.配置点击设置点击 在setting.json中编辑3.setting.json设置 这里我们以cpp为例 偷懒可以直接把下面的json文件做替换 {"workbench.colorTheme": "Quiet Light","remote.…

几行代码,优雅的避免接口重复请求!同事都说好!

背景简介 我们日常开发中,经常会遇到点击一个「按钮」或者进行「搜索」时,请求接口的需求。 如果我们不做优化,连续点击「按钮」或者进行「搜索」,接口会重复请求。❝ 首先,这会导致性能浪费!最重要的,如果接口响应比较慢,此时,我们在做其他操作会有一系列bug! ❞ 那…

Windows远程桌面的奇技淫巧

远程桌面协议(RDP)是一个多通道(multi-channel)的协议,让使用者连上提供微软终端机服务的计算机(称为服务端或远程计算机)。在获取权限后,针对3389进行展开,先查询3389端口是否开启,发现没有开启(也有可能更改了端口),则可以通过注册表进行手动启动。前言Windows远程桌面…

路径规划(2)——A*算法

1、A*算法原理搜索区域(The Search Area):图中的搜索区域被划分为了简单的二维数组,数组每个元素对应一个小方格,当然我们也可以将区域等分成是五角星,矩形等,通常将一个单位的中心点称之为搜索区域节点(Node)。   开放列表(Open List):我们将路径规划过程中待检测…

编译安装Kubernetes 1.29 高可用集群(8)--Dashboard和Traefik安装部署

1.部署Dashboard 1.1 在任意k8s-master节点上安装dashboard # helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/ # helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kube…

我跟你说@RefreshScope跟Spring事件监听一起用有坑!

本文记录一下我在 Spring 自带的事件监听类添加 @RefreshScope 注解时遇到的坑,原本这两个东西单独使用是各自安好,但当大家将它们组合在一起时,会发现我们的事件监听代码被重复执行。希望大家引以为鉴,避免重复踩坑。耐心看完,你一定会有所收获! 前置描述 最近有一个用户…

Vue开发环境搭建教程

在搭建Vue开发环境时,通常需要遵循一系列步骤来确保环境配置正确且高效。以下是一个详细的步骤指南,用于在Windows系统上搭建Vue开发环境: 一、安装Node.js下载Node.js:访问Node.js官网(https://nodejs.org/zh-cn/)下载适合您操作系统的Node.js安装包。安装Node.js:双击…

数据血缘系列(2)——什么是数据血缘?

大家好,我是独孤风。在当今数据驱动的商业环境中,数据治理成为企业成功的关键因素之一。对于数据血缘的定义,一直都有争论,本文我们详细探讨下什么是数据血缘,并说明数据血缘能分析什么。 本文为《数据血缘分析原理与实践 》一书读书笔记,部分观点参考自书中原文,如需更…

windows 运行 java程序时 无故停止不动 问题

windows 运行 java程序时 无故停止不动 问题。是 cmd 程序 的 快速编辑模式 引起的。去掉即可。 右键点属性-》将 快速编辑模式 的多选框 去掉

盒子模型和浮动、溢出属性、圆形头像、定位、模态框z-index、透明度修改

【一】盒子模型和浮动 【1】盒子模型盒子模型(Box Model)是指在网页设计中,用于描述和布局元素的一种模型。 它将每个元素看作是一个具有四个边界的矩形盒子,包括内容区域(content)、内边距(padding)、边框(border)和外边距(margin)。【2】组成部分内容区域(Conte…

W外链短网址生成,他们家的短网址免费的吗?

W外链作为短网址服务的一种,体现了短网址技术的现代发展趋势,它不仅提供了基础的网址缩短功能,还扩展了一系列高级特性和增值服务,以适应更广泛的市场需求。根据相关参考内容,W外链具有以下特点和优势: 短域名与高级设置:W外链提供了非常短的域名,这有助于提高用户体验…