前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【JUC进阶】10. 使用JMH进行性能测试

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

作者头像
有一只柴犬
发布2024-01-25 10:48:24
2900
发布2024-01-25 10:48:24
举报
文章被收录于专栏:JAVA体系

1、前言

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

2、传统的性能测试

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

代码语言:javascript
复制
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相关依赖

代码语言:javascript
复制
<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、编写简单示例

代码语言:javascript
复制
/**
 * @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();
    }
}

执行结果:

代码语言:javascript
复制
# ...... 这里省略部分信息,这些都是描述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度量的模式,或测试方式。

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

5.2、@Benchmark

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

5.3、OptionsBuilder & Options

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

代码语言:javascript
复制
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来配置,也可以使用注解。

代码语言:javascript
复制
Options options = new OptionsBuilder()
        .include(JMHTestHello01.class.getSimpleName())
        .measurementIterations(3).build();    //  执行的次数, 3次

代码语言:javascript
复制
@Measurement(iterations = 3)
@Benchmark
public void dealHelloWorld() throws InterruptedException {
    // 这里模拟该方法执行
    Thread.sleep(1000);
}

5.5、预热(Warmup)

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

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

代码语言:javascript
复制
Options options = new OptionsBuilder()
        .include(JMHTestHello01.class.getSimpleName())
        .warmupIterations(3).build();    //  预热的次数, 3次

代码语言:javascript
复制
@Warmup(iterations = 3)
@Benchmark
public void dealHelloWorld() throws InterruptedException {
    // 这里模拟该方法执行
    Thread.sleep(1000);
}

5.6、状态State

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

代码语言:javascript
复制
@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,线程范围。也就是个对象只会被一个线程访问。在多线程池测试时,会为每一个线程生成一个对象。
代码语言:javascript
复制
@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虽然功能强大,但在使用时需要谨慎选择测试场景和参数,并理解其使用的统计方法和度量指标,以确保测试结果的准确性和可靠性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、前言
  • 2、传统的性能测试
  • 3、什么是JMH
  • 4、Hello JMH
    • 4.1、Maven相关依赖
      • 4.2、编写简单示例
      • 5、基本属性配置
        • 5.1、@BenchmarkMode
          • 5.2、@Benchmark
            • 5.3、OptionsBuilder & Options
              • 5.4、迭代Iteration
                • 5.5、预热(Warmup)
                  • 5.6、状态State
                  • 6、IDEA JMH插件
                  • 7、小结
                  相关产品与服务
                  腾讯云服务器利旧
                  云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档