【JUC进阶】10. 使用JMH进行性能测试

news2024/11/23 20:17:37

目录

1、前言

2、传统的性能测试

2、什么是JMH

3、Hello JMH

3.1、Maven相关依赖

3.2、编写简单示例

4、基本属性配置

4.1、@BenchmarkMode

4.2、@Benchmark

4.3、OptionsBuilder & Options

4.4、迭代Iteration

4.5、预热(Warmup)

4.6、状态State

5、IDEA JMH插件

6、小结


1、前言

软件开发中,除要写出正确的代码之外,还需要写出高效的代码。这在并发编程中更加重要,原因主要有两点。首先,一部分并发程序由串行程序改造而来,其目的就是提高系统性能,因此,自然需要有一种方法对两种算法进行性能比较。其次,由于业务原因引入的多线程有可能因为线程并发控制导致性能损耗,因此要评估损耗的比重是否可以接受。无论出自何种原因需要进行性能评估,量化指标总是必要的。在大部分场合,简单地回答谁快谁慢是远远不够的,如何将程序性能量化呢? 这就是本节要介绍的 Java 微基准测试框架JMH。

2、传统的性能测试

传统的性能测试,一般会在方法前后打印时间戳,然后通过时间差来判断执行的耗时。

public static void dealHelloWorld() throws InterruptedException {
    // 这里模拟该方法执行
    Thread.sleep(1000);
}

public static void main(String[] args) throws InterruptedException {
    long start0 = System.currentTimeMillis();
    dealHelloWorld();
    long end0 = System.currentTimeMillis();
    System.out.println("执行耗时:" + (end0-start0) + "ms");
}

执行结果:

但是如果代码量较大,而且较为复杂的话,通常需要打印较多的时间戳,然后分段进行计算。就像这样:

这样的话,一方面业务代码中会融入很多的计算时间的代码,增加代码可阅读性;另一方面由于JVM可能会对代码进行运行时优化,比如循环展开、运行时编译等,这样会导致某组未经优化的性能数据参与统计计算。那么这时候就需要JMH了。

3、什么是JMH

JMH(Java Microbenchmark Harness)是Java语言的微基准测试框架,用于准确、可靠地测量和评估Java代码的性能。它是由OpenJDK团队开发的,专门针对Java应用程序的性能测试和基准测试。通过JMH 可以对多个方法的性能进行定量分析。比如,当要知道执行一个函数需要多少时间,或者当对一个算法有多种不同实现时,需要选取性能最好的那个。

JMH官网地址:OpenJDK: jmh

Github地址:https://github.com/openjdk/jmh/tags

4、Hello JMH

我们先来简单尝试使用一下。要使用JMH测试很简单,我们可以联想一下Junit单元测试步骤:

  1. 添加junt相关依赖
  2. 声明测试类,@SpringbootTest,如果使用Mock,需要声明Mock相关初始配置
  3. 声明测试套件,@JunitSuit;也可以直接编写测试类@Test

同样的,JMH也是这些步骤,只是依赖包些许不同。

4.1、Maven相关依赖

<dependencies>

    <!-- JMH核心代码 -->
    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-core</artifactId>
        <version>1.35</version>
    </dependency>

    <!-- JMH注解相关依赖 -->
    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-generator-annprocess</artifactId>
        <version>1.35</version>
    </dependency>
</dependencies>

4.2、编写简单示例

/**
 * @author Shamee loop
 * @date 2023/7/1
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.SECONDS)
public class JMHTestHello01 {

    /**
     * @Benchmark 类似于Junit,表示被度量代码标注
     */
    @Benchmark
    public void dealHelloWorld() throws InterruptedException {
        // 这里模拟该方法执行
        Thread.sleep(1000);
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(JMHTestHello01.class.getSimpleName())
                .warmupIterations(3)    //  预热的次数, 3次
                .warmupTime(TimeValue.seconds(2))   // 预热的时间,2s
                .forks(1)   // 测试的执行线程数量
                .build();
        new Runner(options).run();
    }
}

执行结果:

# ...... 这里省略部分信息,这些都是描述JDK和JMH的基础信息,基本信息等同于当下的环境以及option中的配置
# Benchmark: org.shamee.jmh.demo.JMHTestHello01.dealHelloWorld

# Run progress: 0.00% complete, ETA 00:00:56
# Fork: 1 of 1
# ......  这里开始预热测试,我们指定了预热3次
# Warmup Iteration   1: 1.005 s/op
# Warmup Iteration   2: 1.010 s/op
# Warmup Iteration   3: 1.007 s/op
# ...... 这里迭代测试进行了5次,以及每次的时间 
Iteration   1: 1.007 s/op
Iteration   2: 1.011 s/op
Iteration   3: 1.012 s/op
Iteration   4: 1.008 s/op
Iteration   5: 1.007 s/op


Result "org.shamee.jmh.demo.JMHTestHello01.dealHelloWorld":
  1.009 ±(99.9%) 0.009 s/op [Average]
  (min, avg, max) = (1.007, 1.009, 1.012), stdev = 0.002
  CI (99.9%): [1.000, 1.018] (assumes normal distribution)


# Run complete. Total time: 00:00:59

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

# ...... 这里显示的汇总结果,cnt 执行了5次  score最后的结果  Error误差±0.009s units时间单位
Benchmark                      Mode  Cnt  Score   Error  Units
JMHTestHello01.dealHelloWorld  avgt    5  1.009 ± 0.009   s/op

Process finished with exit code 0

5、基本属性配置

通过上面的示例代码可以发现,JMH的使用并不复杂,代码量也并不多;很多的功能都是通过配置注解,或者生成Options的属性来进行配置的。因此我们要更好的使用JMH其他功能,就需要对他的一些基本配置要有所了解。

5.1、@BenchmarkMode

基准测试的模式。只有一个Mode属性。而这个Mode属性表示JMH度量的模式,或测试方式。

/**
 * <p>Benchmark mode declares the default modes in which this benchmark
 * would run. See {@link Mode} for available benchmark modes.</p>
 *
 * <p>This annotation may be put at {@link Benchmark} method to have effect
 * on that method only, or at the enclosing class instance to have the effect
 * over all {@link Benchmark} methods in the class. This annotation may be
 * overridden with the runtime options.</p>
 */
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BenchmarkMode {

    /**
     * @return Which benchmark modes to use.
     * @see Mode
     */
    Mode[] value();

}

Mode提供了多种方式:

  • Throughput整体吞吐量,表示1秒内可以执行多少次调用。
Throughput("thrpt", "Throughput, ops/time")
  • AverageTime 调用的平均时间,指每秒调用所需要的时间。
AverageTime("avgt", "Average time, time/op")
  • SampleTime 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒内,99.99%的调用在xxx毫秒以内”。
SampleTime("sample", "Sampling time")
  • SingleShotTime 以上模式都是默认1次Iteration为1秒,而这个表示只运行一次。往往会把warmup次数设为0,用于测试冷启动时的性能。
SingleShotTime("ss", "Single shot invocation time")
  • All 将上述的几种模式全部执行一遍。
All("all", "All benchmark modes")

5.2、@Benchmark

@Benchmark 类似于@Test,用于告诉JMH测试覆盖哪些方法。只能注解在方法上,有点类似在测试项目进行package时,JMH会针对注解了@Benchmark的方法生成Benchmark方法代码。通常情况下,每个Benchmark方法都运行在独立的进程中,互不干涉。

5.3、OptionsBuilder & Options

这个是配置类,对测试进行配置。通常需要指定一些参数,如执行测试类(include)、使用的进程个数(fork)、预热迭代次数(warmupInterations)等。在配置启动测试时执行,如上述代码:

Options options = new OptionsBuilder()
        .include(JMHTestHello01.class.getSimpleName())
        .warmupIterations(3)    //  预热的次数, 3次
        .warmupTime(TimeValue.seconds(2))   // 预热的时间,2s
        .forks(1)   // 测试的执行线程数量
        .build();

5.4、迭代Iteration

迭代是JMH 的一次测量单位。在大部分测量模式下,一次迭代表示1秒。在这一秒内会不间断调用被测方法,并采样计算吞吐量、平均时间等。

可以使用OptionsBuilder来配置,也可以使用注解。

Options options = new OptionsBuilder()
        .include(JMHTestHello01.class.getSimpleName())
        .measurementIterations(3).build();    //  执行的次数, 3次

@Measurement(iterations = 3)
@Benchmark
public void dealHelloWorld() throws InterruptedException {
    // 这里模拟该方法执行
    Thread.sleep(1000);
}

5.5、预热(Warmup)

由于 Java 虚拟机的 JIT 的存在,同一个方法在 JIT 编译前后的时间将会不同。通常只考虑方法在 JIT 编译后的性能。预热测试不会作为最终的统计结果,预热的目的是让Java虚拟机对被测试代码进行足够多的优化。

同样的,预热也可以通过OptionsBuilder来配置,也可以使用注解。

Options options = new OptionsBuilder()
        .include(JMHTestHello01.class.getSimpleName())
        .warmupIterations(3).build();    //  预热的次数, 3次

@Warmup(iterations = 3)
@Benchmark
public void dealHelloWorld() throws InterruptedException {
    // 这里模拟该方法执行
    Thread.sleep(1000);
}

5.6、状态State

通过 State 可以指定一个对象的作用范围,JMH中通过Scope来进行实例化和共享操作。

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface State {

    /**
     * State scope.
     * @return state scope
     * @see Scope
     */
    Scope value();

}
  1. Scope.Benchmark:基准测试范围。所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能;
  2. Scope.Group:同一个线程在同一个 group 里共享实例
  3. Scope.Thread:默认的 State,线程范围。也就是个对象只会被一个线程访问。在多线程池测试时,会为每一个线程生成一个对象。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JMHTestHello01 {
}

6、IDEA JMH插件

类似Junit测试类的代码生成工具,JMH也有相应的测试代码自动生成工具插件。

下载安装插件 JMH Java Microbenchmark Harness。

安装完成后,在需要i生成测试代码的地方鼠标右键 -> Generate -> Generate JMH Benchmark,就可以自动生成。

然后只需要按照实际需求更改需要测试的属性配置,就可以直接鼠标右键运行,查看结果了。

7、小结

实际项目中,通过使用JMH,开发人员可以准确地测量和分析Java代码的性能,并进行性能调优和优化。它可以帮助开发人员更好地理解代码在不同环境下的性能表现,识别性能瓶颈,并找到优化的方向和策略。但是需要注意的是,JMH虽然功能强大,但在使用时需要谨慎选择测试场景和参数,并理解其使用的统计方法和度量指标,以确保测试结果的准确性和可靠性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一图掌控污水厂运营——远眺污水厂数字孪生平台「捷码精品应用展」

随着我国城市化率的提升与环保意识的逐渐提升&#xff0c;对于污水处理的意识与需求也越来越强&#xff0c;需要处理的污水日益增多&#xff0c;污水厂承担的压力明显变大。 传统污水厂通常通过人工巡检的方式&#xff0c;进行设备和厂区检查和监测&#xff0c;这种方法效率低下…

MODBUSTCP和MODBUSRTU数据帧对比

工具介绍 ModBus Poll 在TCP中模拟客户端&#xff0c;在RTU中模拟主机&#xff1b;&#xff08;请求数据方&#xff09; ModBus Slave 在TCP中模拟服务器&#xff0c;在RTU中模拟从机&#xff1b;&#xff08;响应数据方&#xff09; 数据帧简介 ModBus是一种通信协议&#…

Spring整合Elasticsearch(2)

原生查询 可以查询的范围更精确,当ElasticsearchRepository提供的基本方法无法满足我们所需要的查询可以使用原生的方式查询 Test//原生查询public void naticeQuery(){//创建原生查询构建器对象NativeSearchQueryBuilder queryBuilder new NativeSearchQueryBuilder();//过滤…

【前端】ant-design-pro初体验

什么是Ant Design Pro Ant Design Pro 是一个企业级中后台前端/设计解决方案&#xff0c;它秉承 Ant Design 的设计价值观&#xff0c;致力于在设计规范和基础组件的基础上&#xff0c;继续向上构建&#xff0c;提炼出典型模板/业务组件/配套设计资源&#xff0c;进一步提高企…

第164天:应急响应-挖矿脚本检测指南威胁情报样本定性文件清除入口修复

知识点 #知识点 -网页篡改与后门攻击防范应对指南 主要需了解&#xff1a;异常特征&#xff0c;处置流程&#xff0c;分析报告等 主要需了解&#xff1a;日志存储&#xff0c;Webshell检测&#xff0c;分析思路等 掌握&#xff1a; 中间件日志存储&#xff0c;日志格式内容介绍…

如何给旧电脑安装Linux系统

目录 必要软件下载 下载ultraiso软件 下载linux系统 刻录光盘 U盘启动 必要软件下载 下载ultraiso软件 进入ultraiso官网&#xff0c;链接如下&#xff1a; 最新UltraISO官方免费下载 - UltraISO软碟通中文官方网站 下载linux系统 准备一个8G或者以上的U盘&#xff0c;…

微搭学习路线图,JavaScript入门

目录 1 学习路线1.1 HTML 语义和结构1.2 使用 CSS 布局和美化1.3 使用 JavaScript 开发交互1.4 小程序API1.5 云开发1.6 微搭 2 JavaScript入门2.1 变量2.2 注释2.3 运算符2.4 条件语句2.5 函数2.6 事件 3 示例总结 可多初学的问&#xff0c;微搭学习几个小时够么&#xff0c;几…

学习网页设计html学习总结

学习网页设计html学习总结篇一 转眼间&#xff0c;已到了期末&#xff0c;学习网页设计这门课程也要结束了&#xff0c;虽然时间有点短&#xff0c;但是学过这个几周以后我受益匪浅。 记得最初接触dreamweaver的时候&#xff0c;我很茫然&#xff0c;因为刚接触陌生的软件心里会…

4 springboot配置文件——yaml语法

4.1 yaml配置文件介绍 原先的配置文件是application.properties&#xff0c;但是官方不推荐使用这个&#xff0c;官方推荐使用application.yaml配置文件。因此&#xff0c;将原来的application.properties删除&#xff0c;改为application.yaml。 4.2 yaml配置文件语…

MinIO部署简单使用

前言 前面我们做了分布式文件存储系统FastDFS的部署应用&#xff0c;其安装还是比较繁琐的&#xff0c;而且实际生产的应用限制较大&#xff0c;下面&#xff0c;介绍一款开源的文件系统——MinIO&#xff0c;它是一种对象存储解决方案&#xff0c;提供与 Amazon Web Services…

【回溯算法Part04】| 93.复原IP地址、78.子集、90.子集||

目录 &#x1f388;LeetCode93.复原IP地址 &#x1f388;LeetCode78.子集 &#x1f388; LeetCode90.子集|| &#x1f388;LeetCode93.复原IP地址 链接&#xff1a;93.复原IP地址 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能…

MKS SERVO4257D 闭环步进电机_系列12 STM32_CAN例程

第1部分 产品介绍 具备脉冲接口和RS485/CAN串行接口&#xff0c;支持MODBUS-RTU通讯协议&#xff0c;内置高效FOC矢量算法。 硬件开源&#xff01; 第2部分 相关资料下载 2.1 源代码下载 网盘&#xff1a; 08_例程-STM32系列(CAN) 第3部分 参数配置和注意事项 3.1 电机参…

iPhone 15系规格全面出炉,配置大换血看齐安卓旗舰

网上经常有声音说 iPhone 又不行了&#xff0c;产品竞争力不如安卓云云&#xff0c;事实上是这样的吗&#xff1f; 就拿最近 618 智能手机销售情况来看&#xff0c;苹果一家就以 562.8 万部成绩拿下了近一半市场&#xff0c;营收占比更是高达 67%。 来源&#xff1a;Techlnsig…

OpenCV读取一张8位无符号四通道图像并显示

#include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/opencv.hpp> #include

聊聊Compose跨平台与KMM

作者&#xff1a;黄林晴 有许多开发者可能还没有了解过Compose Multiplatform和KMM&#xff0c;那么本次分享将通过以下几点来介绍Compose Multiplatform 与KMM&#xff0c;让我们一起体验Kotlin跨平台的魅力。 Compose Multiplatform 与 KMM的关系Compose Multiplatform 与 K…

RK3588平台开发系列讲解(Camera篇)V4L2 主要特性

文章目录 一、V4L2 介绍1.1、模块化的架构1.2、统一的设备节点1.3、统一的视频数据格式1.4、支持多种视频设备1.5、支持流式 I/O1.6、支持控制参数1.7、支持事件通知二、V4L2使用场景沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解V4L2 主要特性。 一、…

赛宁数字孪生靶场:构建验证评估业务的新势能

​​2023年5月1日实施的《关键信息基础设施安全保护要求》中对于关基企业的现有网络安全保障体系提出了更加具体全面的要求&#xff0c;人员、系统、体系是检测评估主要关注的方向。 赛宁数字孪生靶场从验证评估业务的角度出发&#xff0c;借助多云融合能力、多样化仿真能力、…

链式编程艺术:探索 Promise 链的美妙之处

文章目录 1. 什么是 Promise&#xff1f;它解决了什么问题&#xff1f;2. Promise 有哪些状态&#xff0c;并且解释每个状态的含义。3. 如何创建一个 Promise&#xff0c;并描述其基本结构和用法。4. 解释 Promise 的链式调用&#xff08;chaining&#xff09;和方法的执行顺序…

RabbitMQ系列(13)--Direct交换机的简介与实现

1、Direct交换机的介绍 Direct交换机能让消息只发送往绑定了指定routingkey的队列中去&#xff0c;值得注意的是当绑定多个队列的routingkey都相同&#xff0c;则这种情况下的表现与Fanout交换机的类似 2、Direct交换机的实现 (1)新建一个名为fanout的包&#xff0c;用于装发…

QT6在线下载安装慢的问题

由于某“墙”的原因&#xff0c;在国内安装QT是会要了老命的&#xff0c;下载只有几十K&#xff0c;安装QT6保守估计得按天计算了。 经过多次尝试&#xff0c;终于找到了可以“几十MB”速度下载安装的办法。 方法一&#xff1a; qt-unified-windows-x64-4.5.2-online.exe --…