Java 微基准测试工具 JMH

#教程 #Java #JMH #工具 [字体 ··]

JMH(Java Microbenchmark Harness)是一个进行基准测试的工具,由 OpenJDK 团队研发,JMH 可以一个方法为维度进行吞吐量、调用时间等测试,精度可以精确到微秒级,JMH 提供注解可以更加便捷的使用。

JMH 使用注意点

  • 测试前需要预热
  • 防止无用代码进入测试方法种
  • 防止代码消除

如何使用 JMH

Maven 依赖:

 1<!-- JMH 基准依赖 -->
 2<dependency>
 3    <groupId>org.openjdk.jmh</groupId>
 4    <artifactId>jmh-core</artifactId>
 5    <version>1.28</version>
 6</dependency>
 7
 8<dependency>
 9    <groupId>org.openjdk.jmh</groupId>
10    <artifactId>jmh-generator-annprocess</artifactId>
11    <version>1.28</version>
12</dependency>

启动程序

此示例程序可以直接启动。

 1// 测试类型,例如吞吐、平均时间等
 2@BenchmarkMode({Mode.Throughput})
 3// 预热,iterations预热次数,time时间限制
 4@Warmup(iterations = 1, time = 2)
 5// 度量方式,iterations执行次数,time时间限制
 6@Measurement(iterations = 1, time = 2)
 7// 使用的线程数
 8@Threads(4)
 9// 使用几个JVM进程跑
10@Fork(1)
11// 成员变量共享方式
12@State(value = Scope.Benchmark)
13// 时间单位
14@OutputTimeUnit(TimeUnit.MILLISECONDS)
15public class SimpleBenchmark {
16
17    int cnt;
18
19    // 初始化方法
20    @Setup
21    public void init() {
22        cnt = 10;
23    }
24
25    @Benchmark
26    public String test() {
27        StringBuilder sb = new StringBuilder();
28        for (int i = 0; i < cnt; i ++) {
29            sb.append(i + 1);
30        }
31        return sb.toString();
32    }
33
34    // 引导JMH启动
35    public static void main(String[] args) throws Exception{
36        Options op = new OptionsBuilder()
37                .include(SimpleBenchmark.class.getSimpleName())
38                .build();
39        new Runner(op).run();
40    }
41}

输出示例:

 1# Blackhole mode: full + dont-inline hint
 2# Warmup: 1 iterations, 2 s each
 3# Measurement: 1 iterations, 2 s each
 4# Timeout: 10 min per iteration
 5# Threads: 4 threads, will synchronize iterations
 6# Benchmark mode: Throughput, ops/time
 7# Benchmark: org.forza.benchmark.SimpleBenchmark.test
 8
 9# Run progress: 0.00% complete, ETA 00:00:04
10# Fork: 1 of 1
11# Warmup Iteration   1: 32499.936 ops/ms
12Iteration   1: 38976.381 ops/ms
13
14
15Result "org.forza.benchmark.SimpleBenchmark.test":
16  38976.381 ops/ms
17
18# Run complete. Total time: 00:00:04
19
20REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
21why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
22experiments, perform baseline and negative tests that provide experimental control, make sure
23the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
24Do not assume the numbers tell you what you want them to tell.
25
26Benchmark              Mode  Cnt      Score   Error   Units
27SimpleBenchmark.test  thrpt       38976.381          ops/ms
28
29Process finished with exit code 0

用 maven 打后使用 java -jar 启动

需要增加一个 Maven Plugin。

 1    <build>
 2        <plugins>
 3            <plugin>
 4                <groupId>org.apache.maven.plugins</groupId>
 5                <artifactId>maven-shade-plugin</artifactId>
 6                <version>2.4.1</version>
 7                <executions>
 8                    <execution>
 9                        <phase>package</phase>
10                        <goals>
11                            <goal>shade</goal>
12                        </goals>
13                        <configuration>
14                            <finalName>benchmark</finalName>
15                            <transformers>
16                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
17                                    <mainClass>org.openjdk.jmh.Main</mainClass>
18                                </transformer>
19                            </transformers>
20                        </configuration>
21                    </execution>
22                </executions>
23            </plugin>
24        </plugins>
25    </build>

运行打包命令,运行 JMH 测试。

1maven package
2java -jar target/benchmark.jar

常用注解

@BenchmarkMode

指定基准测试类型。这里选择的是 Throughput 也就是吞吐量,吞吐量在这里是单位时间内可以执行方法的次数。

  • Throughput - 整体吞吐量,例如“1 秒内可以执行多少次调用”。
  • AverageTime - 调用的平均时间,例如“每次调用平均耗时 xxx 毫秒”。
  • SampleTime - 随机取样,最后输出取样结果的分布,例如“99%的调用在 xxx 毫秒以内,99.99%的调用在 xxx 毫秒以内”
  • SingleShotTime - 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为 0,用于测试冷启动时的性能。 All - 所有模式,执行测试时执行以上所有类型测试

@Warmup

用来对程序进行预热。

为什么需要预热?因为 JVM 的 JIT 机制,一个方法被调用多次之后 JVM 会尝试将其编译为机器码,从而提高其运行速度。

@Measurement

用来指定度量的方式,为基准设置默认测量参数。

参数:

  • iterations : 执行测试的轮数
  • time :每轮的时间
  • timeUnit : 时间单位,默认:秒
  • batchSize:执行方法次数

每轮时间(time)和方法执行次数(batchSize)都是限制,满足其中一个一轮就结束。

@Threads

每个 JVM 进程中的测试线程数量,根据具体情况选择,一般为 CPU 核数 * 2

@Fork

一般指定为 1,既使用一个 JVM 进程运行。 有时一个 JVM 进行执行 benchmark 会存在误差,为了消除这种误差可以指定 Fork 大于 1,此时会多次运行编写的 benchmark。

@OutputTImeUnit

输出结果的时间单位。

@Benchmark

标识一个方法是一个测试。

@Param

类成员级别注解,@Param 可以用来指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能。

@Setup

方法级别注解,标识一个初始化方法,初始化方法在 benchmark 执行前初始执行。

State

当使用 @Setup 参数的时候,必须在类上加这个参数,不然会提示无法运行。

State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。 因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。

  • Thread - 该状态为每个线程独享。
  • Group - 该状态为同一个组里面所有线程共享。
  • Benchmark - 该状态在所有线程间共享。

资料


博客没有评论系统,可以通过 邮件 评论和交流。 Top↑