关于java:别再写-main-方法测试了太-Low这才是专业-Java-测试方法

起源:https://juejin.cn/post/684490…

前言

“If you cannot measure it, you cannot improve it”.

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

然而,如果相熟JVM类加载机制的话,应该晓得JVM默认的执行模式是JIT编译与解释混合执行。JVM通过热点代码统计分析,辨认高频办法的调用、循环体、公共模块等,基于JIT动静编译技术,会将热点代码转换成机器码,间接交给CPU执行。

也就是说,JVM会一直的进行编译优化,这就使得很难确定反复多少次能力失去一个稳固的测试后果?所以,很多有教训的同学会在测试代码前写一段预热的逻辑。

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

Java的基准测试须要留神的几个点:

  • 测试前须要预热。
  • 避免无用代码进入测试方法中。
  • 并发测试。
  • 测试后果出现。

JMH的应用场景:

  1. 定量分析某个热点函数的优化成果
  2. 想定量地晓得某个函数须要执行多长时间,以及执行工夫和输出变量的相关性
  3. 比照一个函数的多种实现形式

本篇次要是介绍JMH的DEMO演示,和罕用的注解参数。心愿能对你起到帮忙。

DEMO 演示

这里先演示一个DEMO,让不理解JMH的同学可能疾速把握这个工具的大略用法。

1. 测试项目构建

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

为了不便,这里间接介绍应用maven构建JMH测试项目的形式。

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

$ mvn archetype:generate \
          -DinteractiveMode=false \
          -DarchetypeGroupId=org.openjdk.jmh \
          -DarchetypeArtifactId=jmh-java-benchmark-archetype \
          -DgroupId=org.sample \
          -DartifactId=test \
          -Dversion=1.0
复制代码

对应目录下会呈现一个test我的项目,关上我的项目后咱们会看到这样的我的项目构造。

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

<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. 编写性能测试

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

/**
 * @author Richard_yyf
 * @version 1.0 2019/8/27
 */

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {
 private static final int SIZE = 10000;

    private List<String> list = new LinkedList<>();

    @Setup
    public void setUp() {
        for (int i = 0; i < SIZE; i++) {
            list.add(String.valueOf(i));
        }
    }

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

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void forEachIterate() {
        for (String s : list) {
            System.out.print("");
        }
    }
}

3. 执行测试

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

生成jar文件的模式次要是针对一些比拟大的测试,可能对机器性能或者实在环境模拟有一些需要,须要将测试方法写好了放在linux环境执行。具体命令如下

$ mvn clean install
$ java -jar target/benchmarks.jar

咱们日常中遇到的个别是一些小测试,比方我下面写的例子,间接在IDE中跑就好了。启动形式如下:

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(LinkedListIterationBenchMark.class.getSimpleName())
            .forks(1)
            .warmupIterations(2)
            .measurementIterations(2)
         .output("E:/Benchmark.log")
            .build();

    new Runner(opt).run();
}

4. 报告后果

输入后果如下,

最初的后果:

Benchmark                                      Mode  Cnt     Score   Error  Units
LinkedListIterationBenchMark.forEachIterate   thrpt    2  1192.380          ops/s
LinkedListIterationBenchMark.forIndexIterate  thrpt    2   206.866          ops/s

整个过程:

# Detecting actual CPU count: 12 detected
# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forEachIterate

# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 1
# Warmup Iteration   1: 1189.267 ops/s
# Warmup Iteration   2: 1197.321 ops/s
Iteration   1: 1193.062 ops/s
Iteration   2: 1191.698 ops/s

Result "org.sample.jmh.LinkedListIterationBenchMark.forEachIterate":
  1192.380 ops/s

# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 12 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate

# Run progress: 50.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration   1: 205.676 ops/s
# Warmup Iteration   2: 206.512 ops/s
Iteration   1: 206.542 ops/s
Iteration   2: 207.189 ops/s

Result "org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate":
  206.866 ops/s

# Run complete. Total time: 00:01:21

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.

Benchmark                                      Mode  Cnt     Score   Error  Units
LinkedListIterationBenchMark.forEachIterate   thrpt    2  1192.380          ops/s
LinkedListIterationBenchMark.forIndexIterate  thrpt    2   206.866          ops/s

注解介绍

上面咱们来具体介绍一下相干的注解,

@BenchmarkMode

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

类型 形容
Throughput 每段时间执行的次数,个别是秒
AverageTime 均匀工夫,每次操作的均匀耗时
SampleTime 在测试中,随机进行采样执行的工夫
SingleShotTime 在每次执行中计算耗时
All 所有模式

能够正文在办法级别,也能够正文在类级别,

@BenchmarkMode(Mode.All)
public class LinkedListIterationBenchMark {
 ...
}
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
public void m() {
 ...
}

@Warmup

这个单词的意思就是预热,iterations = 3就是指预热轮数。

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Warmup(iterations = 3)
public void m() {
 ...
}

@Measurement

正式度量计算的轮数。

  • iterations 进行测试的轮次
  • time 每轮进行的时长
  • timeUnit时长单位
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Measurement(iterations = 3)
public void m() {
 ...
}

@Threads

每个过程中的测试线程。

@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {
 ...
}

@Fork

进行 fork 的次数。如果 fork 数是3的话,则 JMH 会 fork 出3个过程来进行测试。

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Fork(value = 3)
public void m() {
 ...
}

@OutputTimeUnit

基准测试后果的工夫类型。个别抉择秒、毫秒、微秒。

@OutputTimeUnit(TimeUnit.SECONDS)
public class LinkedListIterationBenchMark {
 ...
}

@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两个办法来实现蕴含以及排除的语义
 */
public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            // 蕴含语义
            // 能够用办法名,也能够用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();

    new Runner(opt).run();
}

结语

基于JMH能够对很多工具和框架进行测试,比方日志框架性能比照、BeanCopy性能比照 等,更多的example能够参考官网给出的JMH samples(https://hg.openjdk.java.net/c…)

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.20w 程序员红包封面,快快支付。。。

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理