BlackChen'site

JMH 测试框架

JMH 测试框架

开始使用

        <jmh.version>1.23</jmh.version>
        
        <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>
        

可以使用maven archtype进行自动生成
1586778938425

样例


@BenchmarkMode(Mode.Throughput) // 吞吐量
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 结果所使用的时间单位
@State(Scope.Thread) // 每个测试线程分配一个实例
@Fork(2) // Fork进行的数目
@Warmup(iterations = 2) // 先预热4轮
@Measurement(iterations = 5) // 进行10轮测试
public class MyBenchmark {

    static AtomicInteger integer = new AtomicInteger();

    @Benchmark
    public void testMethod() {
        integer.incrementAndGet();
    }

}

执行

  1. 可以编译为jar包,直接执行
   mvn clean package
   
   java -jar target/benchmark.jar
  1. 也可以编写main方法, 直接执行
    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                .include(MyBenchmark.class.getSimpleName())
                .forks(1)
                .measurementIterations(3)
                .warmupIterations(2)
                .resultFormat(ResultFormatType.JSON)
                .result("log/benchmark_sequence.json")
                .output("log/benchmark_sequence.log")
                .build();
       new Runner(opt).run();
    }

上面显示: 执行MyBenchmark, 使用一个进程执行, 压力测试3轮, 预热2轮output输出到log/benchmark_sequence.log文件, result使用 json格式化, 并输出到log/benchmark_sequence.json文件.

注意 : include 输入的是正则表达式.

输出结果

# JMH version: 1.23
# VM version: JDK 1.8.0_144, Java HotSpot(TM) 64-Bit Server VM, 25.144-b01
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Users/black/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/193.5662.53/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53676:/Users/black/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/193.5662.53/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 1 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.black.demo.MyBenchmark.testMethod

# Run progress: 0.00% complete, ETA 00:02:00
# Fork: 1 of 2
objc[75400]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java (0x10c7bc4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10dff54e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 139598701.042 ops/s
Iteration   1: 152859172.038 ops/s
Iteration   2: 86876920.802 ops/s

# Run progress: 25.00% complete, ETA 00:01:34
# Fork: 2 of 2
objc[75523]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java (0x10501e4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1050944e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 147700155.776 ops/s
Iteration   1: 136090040.091 ops/s
Iteration   2: 147411608.766 ops/s


Result "com.black.demo.MyBenchmark.testMethod":
  130809435.424 ±(99.9%) 194568258.201 ops/s [Average]
  (min, avg, max) = (86876920.802, 130809435.424, 152859172.038), stdev = 30109653.331
  CI (99.9%): [≈ 0, 325377693.625] (assumes normal distribution)


# JMH version: 1.23
# VM version: JDK 1.8.0_144, Java HotSpot(TM) 64-Bit Server VM, 25.144-b01
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Users/black/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/193.5662.53/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53676:/Users/black/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/193.5662.53/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 1 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.black.demo.MyBenchmark2.testMethod

# Run progress: 50.00% complete, ETA 00:01:02
# Fork: 1 of 2
objc[75656]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java (0x10df724c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10dfe84e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 143550017.896 ops/s
Iteration   1: 144959710.003 ops/s
Iteration   2: 143473054.324 ops/s

# Run progress: 75.00% complete, ETA 00:00:31
# Fork: 2 of 2
objc[75787]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/bin/java (0x10cf884c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10e7c04e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 149558611.229 ops/s
Iteration   1: 152600541.752 ops/s
Iteration   2: 151566800.779 ops/s


Result "com.black.demo.MyBenchmark2.testMethod":
  148150026.714 ±(99.9%) 29737702.401 ops/s [Average]
  (min, avg, max) = (143473054.324, 148150026.714, 152600541.752), stdev = 4601942.364
  CI (99.9%): [118412324.313, 177887729.116] (assumes normal distribution)


# Run complete. Total time: 00:02:04

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
MyBenchmark.testMethod   thrpt    4  130809435.424 ± 194568258.201  ops/s
MyBenchmark2.testMethod  thrpt    4  148150026.714 ±  29737702.401  ops/s

Benchmark result is saved to log/benchmark_test.json

Benchmark Mode Cnt Score Error Units
基准测试执行的方法 测试模式,这里是吞吐量 运行多少次 分数 错误 单位
MyBenchmark.testMethod thrpt 4 130809435.424±194568258.201 ops/s
MyBenchmark2.testMethod thrpt 4 148150026.714±29737702.401 ops/s

注意: 最后Error 列输出为空, ±194568258.201 指的是Score列的值.

可视化展示

可以上传JSON文件到 https://jmh.morethan.io/http://deepoove.com/jmh-visual-chart/ 进行可视化对比

1586780891655

注解使用

@BenchmarkMode

Mode 表示 JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。目前 JMH 共有四种模式:

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

@OutputTimeUnit

结果所使用的时间单位

@WarmUp

Warmup 是指在实际进行 Benchmark 前先进行预热的行为。
为什么需要预热?因为 JVMJIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 Benchmark 的结果更加接近真实情况就需要进行预热。

@State

类注解,JMH测试类必须使用 @State 注解,它定义了一个类实例的生命周期,由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

  • Scope.Thread:默认的 State,每个测试线程分配一个实例;
  • Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
  • Scope.Group:每个线程组共享一个实例;

@Fork

进行 fork 的次数。如果 fork 数是2的话,则 JMHfork 出两个进程来进行测试。

@Meansurement

提供真正的测试阶段参数。指定迭代的次数,每次迭代的运行时间和每次迭代测试调用的数量(通常使用 @BenchmarkMode(Mode.SingleShotTime) 测试一组操作的开销——而不使用循环)

  • iterations @return Number of measurement iterations
  • batchSize @return Batch size: number of benchmark method calls per operation

@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次。

@Threads

每个fork进程使用多少条线程去执行你的测试方法, 默认是Runtime.getRuntime().availableProcessors(),

OptionsBuilder 和 注解对比

方法名 参数 作用 对应注解
include 要运行基准测试类的简单名称eg.StringConnectBenchmark 指定要运行的基准测试类 -
exclude 不要运行基准测试类的简单名称eg.StringConnectBenchmark 指定不要运行的基准测试类 -
warmupIterations 预热的迭代次数 指定预热的迭代次数 @Warmup
warmupBatchSize 预热批量的大小 指定预热批量的大小 @Warmup
warmupForks 预热模式:INDI,BULK,BULK_INDI 指定预热模式 @Warmup
warmupMode 预热的模式 指定预热的模式 @Warmup
warmupTime 预热的时间 指定预热的时间 @Warmup
measurementIterations 测试的迭代次数 指定测试的迭代次数 @Measurement
measurementBatchSize 测试批量的大小 指定测试批量的大小 @Measurement
measurementTime 测试的时间 指定测试的时间 @Measurement
mode 测试模式:Throughput(吞吐量),AverageTime(平均时间),SampleTime(在测试中,随机进行采样执行的时间),SingleShotTime(在每次执行中计算耗时),All 指定测试的模式 @BenchmarkMode

实例

JMH Benchmark

评论