JMH - Java 微基准测试工具(自助性能测试)@Benchmark

青苗 青苗 | 171 | 2022-09-13

前言

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

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

31d9ca75267348eea02432e34bd13b03.png

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

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

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

测试前需要预热。
防止无用代码进入测试方法中。
并发测试。
测试结果呈现。
JMH的使用场景:

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

2、开始前的步骤

项目使用的是 Maven,因此只要对 pom.xml 添加依赖即可。

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
    <scope>provided</scope>
</dependency>

3、例子

记得之前和宿友讨论 ArrayList 和 LinkedList 的遍历的性能差别,当时以一种不太妥当的方法进行测试,导致无法得到比较好的结果,刚好这里可以使用这两个来进行比较。

4、代码

4.1 创建业务服务类

package com.wenxiaowu.jmh;
 
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
 
/**
 * 业务服务类
 */
public class BizService {
    private int n = 10000;
    private List<Integer> arrayList;
    private List<Integer> linkedList;
 
    public BizService () {
        arrayList = new ArrayList<>(0);
        linkedList = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
    }
 
    /**
     * 扫描array集合,待测试的方法1
     */
    public void scanArray() {
        for (int i = 0; i < n; i++) {
            this.arrayList.get(i);
        }
    }
 
    /**
     * 扫描list集合,待测试的方法2
     */
    public void scanList() {
        for (int i = 0; i < n; i++) {
            this.linkedList.get(i);
        }
    }
 
    /**
     * 清空数据
     */
    public void clear() {
        for (int i = 0; i < n; i++) {
            this.arrayList.remove(0);
            this.linkedList.remove(0);
        }
    }
}

4.2 创建性能测试代码

package com.wenxiaowu.jmh;
 
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
 
import java.util.concurrent.TimeUnit;
 
@BenchmarkMode(Mode.Throughput) // 吞吐量
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 结果所使用的时间单位
@State(Scope.Thread) // 每个测试线程分配一个实例
@Fork(2) // Fork进行的数目
@Warmup(iterations = 1) // 先预热1轮
@Measurement(iterations = 2) // 进行2轮测试
public class JmhMainApplication {
    BizService bizService;
 
    @Setup(Level.Trial) // 初始化方法,在全部Benchmark运行之前进行
    public void init() {
        bizService = new BizService();
    }
 
    @Benchmark
    public void arrayTraverse() {
        bizService.scanArray();
    }
 
    @Benchmark
    public void listTraverse() {
        bizService.scanList();
    }
 
    @TearDown(Level.Trial) // 结束方法,在全部Benchmark运行之后进行
    public void arrayRemove() {
        bizService.clear();
    }
 
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder().include(JmhMainApplication.class.getSimpleName()).build();
        new Runner(options).run();
    }
}

最终执行的效果

/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57755:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar:/Users/yangbin/Documents/workspace/wenxiaowu-java/wenxiaowu-jmh-test/target/classes:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.3.1.RELEASE/spring-boot-starter-web-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter/2.3.1.RELEASE/spring-boot-starter-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot/2.3.1.RELEASE/spring-boot-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.3.1.RELEASE/spring-boot-autoconfigure-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.3.1.RELEASE/spring-boot-starter-logging-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/yangbin/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/yangbin/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:/Users/yangbin/.m2/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:/Users/yangbin/.m2/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/yangbin/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/yangbin/.m2/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.3.1.RELEASE/spring-boot-starter-json-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.11.0/jackson-databind-2.11.0.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.11.0/jackson-annotations-2.11.0.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.11.0/jackson-core-2.11.0.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.0/jackson-datatype-jdk8-2.11.0.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.0/jackson-datatype-jsr310-2.11.0.jar:/Users/yangbin/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.11.0/jackson-module-parameter-names-2.11.0.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.3.1.RELEASE/spring-boot-starter-tomcat-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.36/tomcat-embed-core-9.0.36.jar:/Users/yangbin/.m2/repository/org/glassfish/jakarta.el/3.0.3/jakarta.el-3.0.3.jar:/Users/yangbin/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.36/tomcat-embed-websocket-9.0.36.jar:/Users/yangbin/.m2/repository/org/springframework/spring-web/5.2.7.RELEASE/spring-web-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-beans/5.2.7.RELEASE/spring-beans-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-webmvc/5.2.7.RELEASE/spring-webmvc-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-aop/5.2.7.RELEASE/spring-aop-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-context/5.2.7.RELEASE/spring-context-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-expression/5.2.7.RELEASE/spring-expression-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-starter-test/2.3.1.RELEASE/spring-boot-starter-test-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-test/2.3.1.RELEASE/spring-boot-test-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/boot/spring-boot-test-autoconfigure/2.3.1.RELEASE/spring-boot-test-autoconfigure-2.3.1.RELEASE.jar:/Users/yangbin/.m2/repository/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.jar:/Users/yangbin/.m2/repository/net/minidev/json-smart/2.3/json-smart-2.3.jar:/Users/yangbin/.m2/repository/net/minidev/accessors-smart/1.2/accessors-smart-1.2.jar:/Users/yangbin/.m2/repository/org/ow2/asm/asm/5.0.4/asm-5.0.4.jar:/Users/yangbin/.m2/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/yangbin/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/2.3.3/jakarta.xml.bind-api-2.3.3.jar:/Users/yangbin/.m2/repository/jakarta/activation/jakarta.activation-api/1.2.2/jakarta.activation-api-1.2.2.jar:/Users/yangbin/.m2/repository/org/assertj/assertj-core/3.16.1/assertj-core-3.16.1.jar:/Users/yangbin/.m2/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/Users/yangbin/.m2/repository/org/junit/jupiter/junit-jupiter/5.6.2/junit-jupiter-5.6.2.jar:/Users/yangbin/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar:/Users/yangbin/.m2/repository/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar:/Users/yangbin/.m2/repository/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar:/Users/yangbin/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.6.2/junit-jupiter-params-5.6.2.jar:/Users/yangbin/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.6.2/junit-jupiter-engine-5.6.2.jar:/Users/yangbin/.m2/repository/org/junit/vintage/junit-vintage-engine/5.6.2/junit-vintage-engine-5.6.2.jar:/Users/yangbin/.m2/repository/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar:/Users/yangbin/.m2/repository/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar:/Users/yangbin/.m2/repository/junit/junit/4.13/junit-4.13.jar:/Users/yangbin/.m2/repository/org/mockito/mockito-core/3.3.3/mockito-core-3.3.3.jar:/Users/yangbin/.m2/repository/net/bytebuddy/byte-buddy/1.10.11/byte-buddy-1.10.11.jar:/Users/yangbin/.m2/repository/net/bytebuddy/byte-buddy-agent/1.10.11/byte-buddy-agent-1.10.11.jar:/Users/yangbin/.m2/repository/org/objenesis/objenesis/2.6/objenesis-2.6.jar:/Users/yangbin/.m2/repository/org/mockito/mockito-junit-jupiter/3.3.3/mockito-junit-jupiter-3.3.3.jar:/Users/yangbin/.m2/repository/org/skyscreamer/jsonassert/1.5.0/jsonassert-1.5.0.jar:/Users/yangbin/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar:/Users/yangbin/.m2/repository/org/springframework/spring-core/5.2.7.RELEASE/spring-core-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-jcl/5.2.7.RELEASE/spring-jcl-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/springframework/spring-test/5.2.7.RELEASE/spring-test-5.2.7.RELEASE.jar:/Users/yangbin/.m2/repository/org/xmlunit/xmlunit-core/2.7.0/xmlunit-core-2.7.0.jar:/Users/yangbin/.m2/repository/org/openjdk/jmh/jmh-core/1.19/jmh-core-1.19.jar:/Users/yangbin/.m2/repository/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar:/Users/yangbin/.m2/repository/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar com.wenxiaowu.jmh.JmhMainApplication
# JMH version: 1.19
# VM version: JDK 1.8.0_221, VM 25.221-b11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57755:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 1 iterations, 1 s each
# Measurement: 2 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.wenxiaowu.jmh.JmhMainApplication.arrayTraverse
 
# Run progress: 0.00% complete, ETA 00:00:12
# Fork: 1 of 2
# Warmup Iteration   1: 218321.177 ops/ms
Iteration   1: 205126.949 ops/ms
Iteration   2: 222546.531 ops/ms
 
# Run progress: 25.00% complete, ETA 00:00:28
# Fork: 2 of 2
# Warmup Iteration   1: 220523.238 ops/ms
Iteration   1: 207016.360 ops/ms
Iteration   2: 220534.484 ops/ms
 
 
Result "com.wenxiaowu.jmh.JmhMainApplication.arrayTraverse":
  213806.081 ±(99.9%) 58169.213 ops/ms [Average]
  (min, avg, max) = (205126.949, 213806.081, 222546.531), stdev = 9001.750
  CI (99.9%): [155636.868, 271975.293] (assumes normal distribution)
 
 
# JMH version: 1.19
# VM version: JDK 1.8.0_221, VM 25.221-b11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57755:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 1 iterations, 1 s each
# Measurement: 2 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.wenxiaowu.jmh.JmhMainApplication.listTraverse
 
# Run progress: 50.00% complete, ETA 00:00:18
# Fork: 1 of 2
# Warmup Iteration   1: 0.018 ops/ms
Iteration   1: 0.013 ops/ms
Iteration   2: 0.018 ops/ms
 
# Run progress: 75.00% complete, ETA 00:00:09
# Fork: 2 of 2
# Warmup Iteration   1: 0.018 ops/ms
Iteration   1: 0.018 ops/ms
Iteration   2: 0.018 ops/ms
 
 
Result "com.wenxiaowu.jmh.JmhMainApplication.listTraverse":
  0.017 ±(99.9%) 0.016 ops/ms [Average]
  (min, avg, max) = (0.013, 0.017, 0.018), stdev = 0.002
  CI (99.9%): [0.001, 0.033] (assumes normal distribution)
 
 
# Run complete. Total time: 00:00:36
 
Benchmark                          Mode  Cnt       Score       Error   Units
JmhMainApplication.arrayTraverse  thrpt    4  213806.081 ± 58169.213  ops/ms
JmhMainApplication.listTraverse   thrpt    4       0.017 ±     0.016  ops/ms
 
Process finished with exit code 0

注解介绍

注解说明
@BenchmarkModeMode 表示 JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。目前 JMH 共有四种模式:Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”,单位是操作数/时间。AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”,单位是时间/操作数。SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
@OutputTimeUnit输出的时间单位。
@IterationIteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 Benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。
@WarmUpWarmup 是指在实际进行 Benchmark 前先进行预热的行为。为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 Benchmark 的结果更加接近真实情况就需要进行预热。
@State类注解,JMH测试类必须使用 @State 注解,它定义了一个类实例的生命周期,可以类比 Spring Bean 的 Scope。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:Scope.Thread:默认的 State,每个测试线程分配一个实例;Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;Scope.Group:每个线程组共享一个实例;
@Fork进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。
@Meansurement提供真正的测试阶段参数。指定迭代的次数,每次迭代的运行时间和每次迭代测试调用的数量(通常使用 @BenchmarkMode(Mode.SingleShotTime) 测试一组操作的开销——而不使用循环)
@Setup方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
@TearDown方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。@Setup/@TearDown注解使用Level参数来指定何时调用fixture: Level.Trial 默认level。全部benchmark运行(一组迭代)之前/之后Level.Iteration 一次迭代之前/之后(一组调用) Level.Invocation 每个方法调用之前/之后(不推荐使用,除非你清楚这样做的目的)
@Benchmark方法注解,表示该方法是需要进行 benchmark 的对象。
@Param成员注解,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。@Param 注解接收一个String数组,在 @Setup 方法执行前转化为为对应的数据类型。多个 @Param 注解的成员之间是乘积关系,譬如有两个用 @Param 注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。
文章标签: Java
推荐指数:

真诚点赞 诚不我欺~

JMH - Java 微基准测试工具(自助性能测试)@Benchmark

点赞 收藏 评论

关于作者

青苗
青苗

青苗幼儿园园长

等级 LV4

粉丝 13

获赞 31

经验 678