前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JMH 简单入门

JMH 简单入门

作者头像
KIWI
发布2020-04-08 11:28:54
9380
发布2020-04-08 11:28:54
举报
文章被收录于专栏:KIWI的碎碎念

JMH 简单入门

[1]什么是 JMH

JMH 是 Java Microbenchmark Harness 的缩写。中文意思大致是 “JAVA 微基准测试套件”。首先先明白什么是“基准测试”。百度百科给的定义如下:

基准测试[2]是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。

可以简单的类比成我们电脑常用的鲁大师,或者手机常用的跑分软件安兔兔之类的性能检测软件。都是按一定的基准或者在特定条件下去测试某一对象的的性能,比如显卡、IO、CPU之类的。

[3]为什么要使用 JMH

基准测试的特质有如下几种:

①、可重复性:可进行重复性的测试,这样做有利于比较每次的测试结果,得到性能结果的长期变化趋势,为系统调优和上线前的容量规划做参考。

②、可观测性:通过全方位的监控(包括测试开始到结束,执行机、服务器、数据库),及时了解和分析测试过程发生了什么。

③、可展示性:相关人员可以直观明了的了解测试结果(web界面、仪表盘、折线图树状图等形式)。

④、真实性:测试的结果反映了客户体验到的真实的情况(真实准确的业务场景+与生产一致的配置+合理正确的测试方法)。

⑤、可执行性:相关人员可以快速的进行测试验证修改调优(可定位可分析)。

可见要做一次符合特质的基准测试,是很繁琐也很困难的。外界因素很容易影响到最终的测试结果。特别对于 JAVA的基准测试。 有些文章会告诉我们 JAVA是 C++编写的,一般来说 JAVA编写的程序不太可能比 C++编写的代码运行效率更好。但是JAVA在某些场景的确要比 C++运行的更高效。不要觉得天方夜谭。其实 JVM随着这些年的发展已经变得很智能,它会在运行期间不断的去优化。 这对于我们程序来说是好事,但是对于性能测试就头疼的。你运行的次数与时间不同可能获得的结果也不同,很难获得一个比较稳定的结果。对于这种情况,有一个解决办法就是大量的重复调用,并且在真正测试前还要进行一定的预热,使结果尽可能的准确。

除了这些,对于结果我们还需要一个很好的展示,可以让我们通过这些展示结果判断性能的好坏。

而这些JMH都有!?

[4]如何使用 JMH

下面我们以字符串拼接的几种方法为例子使用JMH做基准测试。

[5]1. 导入依赖

JMH是 JDK9自带的,如果你是 JDK9 之前的版本也可以通过导入 openjdk

代码语言:javascript
复制
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
</dependency>

[6]2. 目录结构

代码语言:javascript
复制
.
├── pom.xml
└── src
   ├── main
   │  └── java
   │     └── cn
   │        └── coder4j
   │           └── study
   │              └── demo
   │                 └── jmh
   │                    ├── benchmark
   │                    │  └── StringConnectBenchmark.java
   │                    └── runner
   │                       └── StringBuilderRunner.java
   └── test
      └── java
         └── cn
            └── coder4j
               └── study
                  └── demo

[7]3. 具体代码

•StringBuilderRunner.java

代码语言:javascript
复制
/**
 * coder4j.cn
 * Copyright (C) 2013-2018 All Rights Reserved.
 */
package cn.coder4j.study.demo.jmh.runner;

import cn.coder4j.study.demo.jmh.benchmark.StringConnectBenchmark;
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 buhao
 * @version StringBuilderRunner.java, v 0.1 2018-12-25 09:53 buhao
 */
public class StringBuilderRunner {

    public static void main( String[] args ) throws RunnerException {
        Options opt = new OptionsBuilder()
                // 导入要测试的类
                .include(StringConnectBenchmark.class.getSimpleName())
                // 预热5轮
                .warmupIterations(5)
                // 度量10轮
                .measurementIterations(10)
                .mode(Mode.Throughput)
                .forks(3)
                .build();

        new Runner(opt).run();


    }

}

•StringConnectBenchmark.java

代码语言:javascript
复制
/**
 * coder4j.cn
 * Copyright (C) 2013-2018 All Rights Reserved.
 */
package cn.coder4j.study.demo.jmh.benchmark;

import org.openjdk.jmh.annotations.Benchmark;

/**
 * @author buhao
 * @version StringConnectBenchmark.java, v 0.1 2018-12-25 09:29 buhao
 */
public class StringConnectBenchmark {

    /**
     * 字符串拼接之 StringBuilder 基准测试
     */
    @Benchmark
    public void testStringBuilder() {
        print(new StringBuilder().append(1).append(2).append(3).toString());
    }

    /**
     * 字符串拼接之直接相加基准测试
     */
    @Benchmark
    public void testStringAdd() {
        print(new String()+ 1 + 2 + 3);
    }

    /**
     * 字符串拼接之String Concat基准测试
     */
    @Benchmark
    public void testStringConcat() {
        print(new String().concat("1").concat("2").concat("3"));
    }

    /**
     * 字符串拼接之 StringBuffer 基准测试
     */
    @Benchmark
    public void testStringBuffer() {
        print(new StringBuffer().append(1).append(2).append(3).toString());
    }

    /**
     * 字符串拼接之 StringFormat 基准测试
     */
    @Benchmark
    public void testStringFormat(){
        print(String.format("%s%s%s", 1, 2, 3));
    }

    public void print(String str) {

    }
}

[8]4. 运行结果

代码语言:javascript
复制
# Run progress: 93.33% complete, ETA 00:00:15
# Fork: 3 of 3
objc[12440]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/bin/java (0x106a7d4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x106af74e0). One of the two will be used. Which one is undefined.
# Warmup Iteration   1: 747281.755 ops/s
# Warmup Iteration   2: 924220.081 ops/s
# Warmup Iteration   3: 1129741.585 ops/s
# Warmup Iteration   4: 1135268.541 ops/s
# Warmup Iteration   5: 1062994.936 ops/s
Iteration   1: 1142834.160 ops/s
Iteration   2: 1143207.472 ops/s
Iteration   3: 1178363.827 ops/s
Iteration   4: 1156408.897 ops/s
Iteration   5: 1123123.829 ops/s
Iteration   6: 1086029.992 ops/s
Iteration   7: 1108795.147 ops/s
Iteration   8: 1125522.731 ops/s
Iteration   9: 1120021.744 ops/s
Iteration  10: 1119916.181 ops/s


Result "cn.coder4j.study.demo.jmh.benchmark.StringConnectBenchmark.testStringFormat":
  1132633.183 ±(99.9%) 16252.303 ops/s [Average]
  (min, avg, max) = (1082146.355, 1132633.183, 1182418.648), stdev = 24325.684
  CI (99.9%): [1116380.879, 1148885.486] (assumes normal distribution)


# Run complete. Total time: 00:03:57

Benchmark                                  Mode  Cnt          Score         Error  Units
StringConnectBenchmark.testStringAdd      thrpt   30   63728919.269 ±  906608.141  ops/s
StringConnectBenchmark.testStringBuffer   thrpt   30  112423521.098 ± 1157072.848  ops/s
StringConnectBenchmark.testStringBuilder  thrpt   30  110558976.274 ±  654163.111  ops/s
StringConnectBenchmark.testStringConcat   thrpt   30   44820009.200 ±  524305.660  ops/s
StringConnectBenchmark.testStringFormat   thrpt   30    1132633.183 ±   16252.303  ops/s

[9]5. 代码解析

•StringBuilderRunner

这个 runner 类的作用,就是启动基准测试。 JMH 通常有两种方式启动,一种就是通过命令行使用 maven 命令执行。这种适合对于大型基准测试,像那些要运行很多很多次,并且运行的时间也很长的情况下。你可以直接打个 jar包,发到服务器上,敲个命令就不用管它,过几十分钟、几小时、几天的时间再回来看结果。

但是很多情况下,我们只是想简单测试一个小功能,没必要还要搞台服务器去跑。所以 JMH 还提供了一种通过 Main方法运行的方式,就如上面代码所示。

在 Main 方法中,通过 org.openjdk.jmh.runner.Runner 类去运行 org.openjdk.jmh.runner.options.Options 实例即可。这里的重点在于 Options 对象的构建。官方提供了一个OptionsBuilder对象去构建。这个 Builder对象是流式的。它的常用方法及对应的注解形式如下:

方法名

参数

作用

对应注解

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

•StringConnectBenchmark

这个就是真正执行基准测试的类,这个类很像单元测试的类,每个测试方法中写上你要执行的测试代码。只不过这里把@Test换成了@Benchmark注解。 而加上了这个就指明这个方法是基准测试方法,当 Runner类的 Main方法运行时,它就会找这些被注解修饰的方法,再按指定的规则去进行基准测试。当然可能不同的方法有时候需要不同的规则,这个时间可以通过上面方法对应的注解形式去单独指定某个方法的规则即可。

[10]6. 结果解析

结果主要分成三个部分。 第一部分以 “#Warmup Iteration。。。。”这种形式的内容。这表明每次预热迭代的结果。 另一部分以“Iteration。。。”形式内容,这表明每次基准测试迭代的结果。

最后一部分以“Result。。。”形式的内容,这就是所有迭代跑完最终的结果。第一段结果告诉了我们最大值、最小值、平均值的信息。 而最最后的表格结构的信息才是我们分析的重点,但是它输出的结果有点错位,刚开始我一直在纠结 Error是± 906608.141代表什么意思,google了一圈发现,Error它其实什么都没输出,而且 Score 是63728919.269 ± 906608.141。我用表格排板了一下,解释如下:

Benchmark

Mode

Cnt

Score

Error

Units

基准测试执行的方法

测试模式,这里是吞吐量

运行多少次

分数

错误

单位

StringConnectBenchmark.testStringAdd

thrpt

30

63728919.269 ± 906608.141

ops/s

StringConnectBenchmark.testStringBuffer

thrpt

30

112423521.098 ± 1157072.848

ops/s

StringConnectBenchmark.testStringBuilder

thrpt

30

110558976.274 ± 654163.111

ops/s

StringConnectBenchmark.testStringConcat

thrpt

30

44820009.200 ± 524305.660

ops/s

StringConnectBenchmark.testStringFormat

thrpt

30

1132633.183 ± 16252.303

ops/s

结论:

StringBuffer >= StringBuilder > String直接相加 > StringConcat >> StringFormat

可见 StringBuffer 与 StringBuilder 大致性能相同,都比直接相加高几个数量级,而且直接相加与 Concat 方法相加差不多。但是这里不管哪种都比 StringFormat高 N 个数量级。所以 String的 Format方法一定要慎用、不用、禁用!!!

[11]相关链接

[12]参考链接

1.

openjdk 官方 DEMO[13]

2.

openjdk 官方 DEMO (翻译版)[14]

3.

浅谈基准测试[15]

4.

什么是基准测试[16]

5.

JMH 学习笔记[17] ← 很不错

6.

使用 JMH 做 JAVA 基准测试[18]

7.

JMH 做 JAVA 基准测试[19]

[20]代码链接

1.DEMO 代码链接[21]

References

[1] : #bdprtz [2] 基准测试: https://baike.baidu.com/item/%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/5876292 [3] : #9v8igk [4] : #6yfwmc [5] : #g0v4hb [6] : #hqfcgh [7] : #kzg1rm [8] : #gspdgn [9] : #u9iswx [10] : #1iwmey [11] : #rsglln [12] : #lbmibh [13] openjdk 官方 DEMO: http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ [14] openjdk 官方 DEMO (翻译版): https://github.com/Childe-Chen/goodGoodStudy/tree/master/src/main/java/com/cxd/benchmark [15] 浅谈基准测试: https://www.cnblogs.com/imyalost/p/9630843.html [16] 什么是基准测试: http://www.blogjava.net/qileilove/archive/2012/07/05/382241.html [17] JMH 学习笔记: https://blog.csdn.net/myjcxd/article/details/83501333 [18] 使用 JMH 做 JAVA 基准测试: https://www.jianshu.com/p/bcb0b60c7ca7 [19] JMH 做 JAVA 基准测试: http://irfen.me/java-jmh-simple-microbenchmark/ [20] : #bvnqgz [21] DEMO 代码链接: https://github.com/kiwiflydream/study-demo/tree/master/study-jmh-demo

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 KIWI的碎碎念 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JMH 简单入门
  • [1]什么是 JMH
  • [3]为什么要使用 JMH
  • [4]如何使用 JMH
    • [5]1. 导入依赖
      • [6]2. 目录结构
        • [7]3. 具体代码
          • [8]4. 运行结果
            • [9]5. 代码解析
              • [10]6. 结果解析
              • [11]相关链接
                • [12]参考链接
                  • [20]代码链接
                    • References
                    相关产品与服务
                    数据库
                    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档