JMH 微基准测试套件

环境

MacBook Pro
Java 1.8

前言

无意中看到JMH – 用来测试方法性能的东东。
好吧,我就了解下~ 未来说不定用得到。

JMH

名字就是标题上名称:Java 微基准测试套件
英文名叫:Java Microbenchmark Harness
JMHJava9中作为JDK的一部分已经引入了;
但是我是Java8,又是gradle项目;需要引入Open-jdk中相应的依赖才行。

build.gradle

在 build.gradle 文件中添加:

    compile group: 'org.openjdk.jmh', name: 'jmh-core', version: '1.23'
    compile('org.openjdk.jmh:jmh-generator-annprocess:1.23')

就可以了,接下来就是写代码了

写测试代码

package jmh;

import org.openjdk.jmh.annotations.Benchmark;

/**
 * @author yutao
 * @since 2020/6/3 5:00 下午
 */
public class StringConnectBenchmark {

    @Benchmark
    public void testStringBuilder() {
        print(new StringBuilder().append(1).append(2).append(3).toString());
    }

    @Benchmark
    public void testStringAdd() {
        print(new String() + 1 + 2 + 3);
    }

    @Benchmark
    public void testStringConcat() {
        print(new String().concat("1").concat("2").concat("3"));
    }

    @Benchmark
    public void testStringBuffer() {
        print(new StringBuffer().append(1).append(2).append(3).toString());
    }

    @Benchmark
    public void testStringFormat() {
        print(String.format("%s%s%s", 1, 2, 3));
    }

    private void print(String toString) {

    }

}

启动代码

package jmh;

import org.openjdk.jmh.annotations.Mode;
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;

/**
 * @author yutao
 * @since 2020/6/3 5:00 下午
 */
public class StringBuilderRunner {

    public static void main(String[] args) throws RunnerException {

        Options opt = new OptionsBuilder().include(StringConnectBenchmark.class.getSimpleName())
                .warmupIterations(5)
                .measurementIterations(10)
                .mode(Mode.Throughput)
                .forks(3)
                .build();

        new Runner(opt).run();
    }

}

执行这个main方法就可以了。

遇到错误

ERROR: Unable to find the resource: /META-INF/BenchmarkList

假设遇到上面的错误,可能是下面这个依赖的范围没有控制好。:

compile('org.openjdk.jmh:jmh-generator-annprocess:1.23')

比如我之前写的是:

testCompile('org.openjdk.jmh:jmh-generator-annprocess:1.23')
// 或
providedCompile('org.openjdk.jmh:jmh-generator-annprocess:1.23')

上面两种写法都会报那个错误,至少我是

结果

正常情况下,应该会有如下结果:

# JMH version: 1.23
# VM version: JDK 1.8.0_211, Java HotSpot(TM) 64-Bit Server VM, 25.211-b12
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57632:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: jmh.StringConnectBenchmark.testStringFormat

# Run progress: 80.00% complete, ETA 00:07:48
# Fork: 1 of 3
# Warmup Iteration   1: 1016531.265 ops/s
# Warmup Iteration   2: 1046406.204 ops/s
# Warmup Iteration   3: 1107229.841 ops/s
# Warmup Iteration   4: 1055051.289 ops/s
# Warmup Iteration   5: 1044207.190 ops/s
Iteration   1: 1127695.392 ops/s
Iteration   2: 1034626.224 ops/s
Iteration   3: 887208.217 ops/s
Iteration   4: 1041210.542 ops/s
Iteration   5: 1071716.734 ops/s
Iteration   6: 1024996.131 ops/s
Iteration   7: 1062593.555 ops/s
Iteration   8: 1087824.436 ops/s
Iteration   9: 1101977.356 ops/s
Iteration  10: 1166143.528 ops/s

# Run progress: 86.67% complete, ETA 00:05:12
# Fork: 2 of 3
# Warmup Iteration   1: 1197293.287 ops/s
# Warmup Iteration   2: 985332.916 ops/s
# Warmup Iteration   3: 1071488.561 ops/s
# Warmup Iteration   4: 1171514.666 ops/s
# Warmup Iteration   5: 807647.157 ops/s
Iteration   1: 984665.944 ops/s
Iteration   2: 1024186.487 ops/s
Iteration   3: 1140789.396 ops/s
Iteration   4: 1085807.609 ops/s
Iteration   5: 950306.564 ops/s
Iteration   6: 644449.235 ops/s
Iteration   7: 914517.484 ops/s
Iteration   8: 925476.242 ops/s
Iteration   9: 901025.908 ops/s
Iteration  10: 948277.296 ops/s

# Run progress: 93.33% complete, ETA 00:02:36
# Fork: 3 of 3
# Warmup Iteration   1: 999728.053 ops/s
# Warmup Iteration   2: 819815.425 ops/s
# Warmup Iteration   3: 906965.258 ops/s
# Warmup Iteration   4: 880806.121 ops/s
# Warmup Iteration   5: 851683.139 ops/s
Iteration   1: 900068.389 ops/s
Iteration   2: 844397.005 ops/s
Iteration   3: 1083297.259 ops/s
Iteration   4: 1091724.838 ops/s
Iteration   5: 1119853.012 ops/s
Iteration   6: 1153083.290 ops/s
Iteration   7: 1163449.184 ops/s
Iteration   8: 1150650.891 ops/s
Iteration   9: 1163546.343 ops/s
Iteration  10: 1018446.675 ops/s


Result "jmh.StringConnectBenchmark.testStringFormat":
  1027133.706 ±(99.9%) 79567.642 ops/s [Average]
  (min, avg, max) = (644449.235, 1027133.706, 1166143.528), stdev = 119093.109
  CI (99.9%): [947566.064, 1106701.347] (assumes normal distribution)

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

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
StringConnectBenchmark.testStringAdd      thrpt   30  35317702.705 ±  1336860.556  ops/s
StringConnectBenchmark.testStringBuffer   thrpt   30  94176455.257 ± 24771740.088  ops/s
StringConnectBenchmark.testStringBuilder  thrpt   30  88236481.722 ± 12615288.187  ops/s
StringConnectBenchmark.testStringConcat   thrpt   30  41883416.348 ±  2917355.393  ops/s
StringConnectBenchmark.testStringFormat   thrpt   30   1027133.706 ±    79567.642  ops/s

Process finished with exit code 0

结果分析

这个结果怎么看呢?

我们先来看下代码的配置:

在这里插入图片描述

结果主要由四部分组成
① Warmup Iteration 1: 1016531.265 ops/s 这种格式数据
这就是预热数据执行的结果

② Iteration 5: 1119853.012 ops/s 这种格式的数据

这部分是正式基准测试迭代的结果

③ Result … … 这个格式数据

这部分就是数据跑完了最后结果。
比如:(min, avg, max) = (644449.235, 1027133.706, 1166143.528) 最小值,平均值,最大值。

④ Run complete. Total time 这中格式的数据

BenchmarkModeCntScoreErrorUnits
StringConnectBenchmark.testStringAddthrpt3035317702.705 ± 1336860.556ops/s
StringConnectBenchmark.testStringBufferthrpt3094176455.257 ± 24771740.088ops/s
StringConnectBenchmark.testStringBuilderthrpt3088236481.722 ± 12615288.187ops/s
StringConnectBenchmark.testStringConcatthrpt3041883416.348 ± 2917355.393ops/s
StringConnectBenchmark.testStringFormatthrpt301027133.706 ± 79567.642ops/s

其中Cnt 是count的缩写

默认场景下,JMH 会找寻标注了@Benchmark类型的方法,可能会跑一些你所不需要的测试,这样就需要通过include和exclude两个方法来完成包含以及排除的语义。 我这里指定就是StringConnectBenchmark类来跑测试。

名称含义
warmupIterations预热次数
measurementIterations正式测试次数
mode使用的模式
forks工做几轮测试
All顾名思义,所有模式,这个在内部测试中常用

每轮正式之前,都会执行预热

测试类型mode有:

测试类型含义
Throughput吞吐量
AverageTime平均时间
SampleTime在测试时,随机采样执行的时间
SingleShotTime测量单次操作的时间,通过调用Benchmark一次并测量其时间

使用这些模式也非常简单,只需要增加@BenchmarkMode注解即可,例如:

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

}

为什么要预热呢?

在使用Java编程过程中,我们对于一些代码调用的细节有多种编写方式,但是不确定它们性能时,往往采用重复多次计数的方式来解决。但是随着JVM不断的进化,随着代码执行次数的增加,JVM会不断的进行编译优化,使得重复多少次才能够得到一个稳定的测试结果变得让人疑惑,这时候有经验的同学就会在测试执行前先循环上万次并注释为预热。

没错!这样做确实可以获得一个偏向正确的测试结果,但是我们试想如果每到需要斟酌性能的时候,都要根据场景写一段预热的逻辑吗?当预热完成后,需要多少次迭代来进行正式内容的测量呢?每次测试结果的输出报告是不是都需要用System.out来输出呢?

其实这些工作都可以交给 JMH (the Java Microbenchmark Harness) ,它被作为Java9的一部分来发布,但是我们完全不需要等待Java9,而可以方便的使用它来简化我们测试,它能够照看好JVM的预热、代码优化,让你的测试过程变得更加简单。

参考地址:

使用JMH做Java微基准测试

【基准测试】JMH 简单入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值