Java 官方性能测试工具 JMH 简单入门

什么是 JMH

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

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

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

为什么要使用 JMH

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

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

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

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

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

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

可见要做一次符合特质的基准测试,是很繁琐也很困难的。外界因素很容易影响到最终的测试结果。特别对于 JAVA的基准测试。


有些文章会告诉我们 JAVA是 C++编写的,一般来说 JAVA编写的程序不太可能比 C++编写的代码运行效率更好。但是JAVA在某些场景的确要比 C++运行的更高效。不要觉得天方夜谭。其实 JVM随着这些年的发展已经变得很智能,它会在运行期间不断的去优化。


这对于我们程序来说是好事,但是对于性能测试就头疼的。你运行的次数与时间不同可能获得的结果也不同,很难获得一个比较稳定的结果。对于这种情况,有一个解决办法就是大量的重复调用,并且在真正测试前还要进行一定的预热,使结果尽可能的准确。

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

而这些JMH都有!?

如何使用 JMH

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

1. 导入依赖

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

<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>

2. 目录结构

.
├── pom.xml
└── src├── main│  └── java│     └── cn│        └── coder4j│           └── study│              └── demo│                 └── jmh│                    ├── benchmark│                    │  └── StringConnectBenchmark.java│                    └── runner│                       └── StringBuilderRunner.java└── test└── java└── cn└── coder4j└── study└── demo

3. 具体代码

  • StringBuilderRunner.java
/*** 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
/*** 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 基准测试*/@Benchmarkpublic void testStringBuilder() {print(new StringBuilder().append(1).append(2).append(3).toString());}/*** 字符串拼接之直接相加基准测试*/@Benchmarkpublic void testStringAdd() {print(new String()+ 1 + 2 + 3);}/*** 字符串拼接之String Concat基准测试*/@Benchmarkpublic void testStringConcat() {print(new String().concat("1").concat("2").concat("3"));}/*** 字符串拼接之 StringBuffer 基准测试*/@Benchmarkpublic void testStringBuffer() {print(new StringBuffer().append(1).append(2).append(3).toString());}/*** 字符串拼接之 StringFormat 基准测试*/@Benchmarkpublic void testStringFormat(){print(String.format("%s%s%s", 1, 2, 3));}public void print(String str) {}
}

4. 运行结果

# 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/sResult "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.684CI (99.9%): [1116380.879, 1148885.486] (assumes normal distribution)# Run complete. Total time: 00:03:57Benchmark                                  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

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方法运行时,它就会找这些被注解修饰的方法,再按指定的规则去进行基准测试。当然可能不同的方法有时候需要不同的规则,这个时间可以通过上面方法对应的注解形式去单独指定某个方法的规则即可。

6. 结果解析


结果主要分成三个部分。


第一部分以 “#Warmup Iteration。。。。”这种形式的内容。这表明每次预热迭代的结果。


另一部分以“Iteration。。。”形式内容,这表明每次基准测试迭代的结果。

最后一部分以“Result。。。”形式的内容,这就是所有迭代跑完最终的结果。第一段结果告诉了我们最大值、最小值、平均值的信息。


而最最后的表格结构的信息才是我们分析的重点,但是它输出的结果有点错位,刚开始我一直在纠结 Error是± 906608.141代表什么意思,google了一圈发现,Error它其实什么都没输出,而且 Score 是63728919.269 ± 906608.141。我用表格排板了一下,解释如下:

BenchmarkModeCntScoreErrorUnits
基准测试执行的方法测试模式,这里是吞吐量运行多少次分数错误单位
StringConnectBenchmark.testStringAddthrpt3063728919.269 ± 906608.141ops/s
StringConnectBenchmark.testStringBufferthrpt30112423521.098 ± 1157072.848ops/s
StringConnectBenchmark.testStringBuilderthrpt30110558976.274 ± 654163.111ops/s
StringConnectBenchmark.testStringConcatthrpt3044820009.200 ± 524305.660ops/s
StringConnectBenchmark.testStringFormatthrpt301132633.183 ± 16252.303ops/s

结论:

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

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

作者:KiwiFly

链接:https://www.jianshu.com/p/c9186119f3d1

来源:简书

更多 Java 原创文章,请关注我微信公众号 「Java中文社群」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546334.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis-Plus基础之Mapper查询

文章目录 一、简单查询二、 分页查询三、条件查询&#xff1a;WrapperWrapper 查询所有Wrapper 查询的 3 种写法一个复杂一点的例子 四、逻辑条件的组合与与和或或与或混用 五、条件为 null 的处理技巧六、设置查询列七、使用 SQL 聚合函数八、模糊查询 一、简单查询 // 根据 …

c# 赋值运算符_C#程序演示赋值运算符的示例

c# 赋值运算符Assignment operators (Assignment () and compound assignments (, -, *, /, %)) are used to assign the value or an expressions result to the left side variable, following are the set of assignment operators, 赋值运算符(Assignment( )和复合赋值( …

Linux Debian11使用国内源安装 Docker 环境

首先切换到root账户&#xff1a; su 一、Debian安装Docker 1.更新并安装一些必要系统工具。 sudo apt-get update sudo apt-get upgrade sudo apt-get install \apt-transport-https \software-properties-common \ca-certificates \curl \gnupg \lsb-release2.安装GPG证书…

HashMap 为什么在链表长度为 8 的时候转红黑树,为啥不能是 9 是 10?

这个问题是在面试某公司的时候面试官提的问题&#xff0c;当时没回答上来。归根到底还是因为自己复习基础的时候还不够仔细&#xff0c;也缺乏思考。 首先 我觉得需要确认一下&#xff0c;是不是随便什么情况下只要满足了链表长度为8就转红黑树呢&#xff1f;答案自然不是&am…

计算机网络怎么寻址_计算机网络中的无类寻址

计算机网络怎么寻址To reduce the wastage of IP addresses in blocks we subnetting. But in Classless addressing wastage of IP addresses in a block is more reduced than Classful subnetting. In this variable length, blocks are used that belongs to no class. 为了…

Linux Debian常用下载工具Transmission和qbittorrent

1.Transmission Transmission是Linux Debian系统下的系统自带的一种BitTorrent客户端下载工具&#xff0c;下载速度比较快。在Linux系统可以替代windows上的迅雷下载工具。 2.qbittorrent 使用下面命令安装&#xff1a; sudo apt-get install qbittorrent获取更多资料&#x…

《 面试又翻车了》这次竟然和 Random 有关?

小强最近面试又翻车了&#xff0c;然而令他郁闷的是&#xff0c;这次竟然是栽到了自己经常在用的 Random 上......面试问题既然已经有了 Random 为什么还需要 ThreadLocalRandom&#xff1f;正文Random 是使用最广泛的随机数生成工具了&#xff0c;即使连 Math.random() 的底层…

c#串口程序接收数据并打印_C#程序可打印各种数据类型的大小

c#串口程序接收数据并打印In this C# program – we are going to print size of various data types, to print size of a type, we use sizeof() operator. 在此C&#xff03;程序中–我们将打印各种数据类型的大小&#xff0c;并使用typeof()运算符打印类型的大小。 It acc…

Linux Debian11使用国内源安装Podman环境

一、Podman简介 Podman 是一个开源的容器运行时项目&#xff0c;可在大多数 Linux 平台上使用。Podman 提供与 Docker 非常相似的功能。正如前面提到的那样&#xff0c;它不需要在你的系统上运行任何守护进程&#xff0c;并且它也可以在没有 root 权限的情况下运行。 Podman 可…

二叉搜索树中第k大元素_二叉搜索树中第K个最小元素

二叉搜索树中第k大元素Problem statement: 问题陈述&#xff1a; Find the k-th smallest element in a given binary search tree (BST). 在给定的二进制搜索树(BST)中找到第k个最小的元素。 Example: 例&#xff1a; K4Kth smallest element in the above binary tree is:…

阿里巴巴Java开发手册建议设置HashMap的初始容量,但设置多少合适呢?

作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;集合是Java开发日常开发中经常会使用到的&#xff0c;而作为一种典型的K-V结构的数据结构&#xff0c;HashMap对于Java开发者一定不陌生。关于HashMap&#xff0c;很多人都对他有一些基本的了解&…

面向.Net程序员的dump分析

背景 Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。在 Windows 系统上&#xff0c; dump 文件分为内核 dump 和用户态 dump 两种。前者一般用来分析内核相关的问题&#xff0c;比如驱动程序&#xff1b;后者一般用来分析用户态程序的问题。 一般…

Linux Debian利用Dockefile将Python的py文件项目代码打包为Docker Podman镜像

1.创建PyCharm工程 使用PyCharm创建testHelloWorld工程&#xff0c;如下图所示&#xff1a; 2.选择本项目下的Python解释器 通过File -> Setting…选择解释器为本工程下的Python解释器。 【备注&#xff1a;一定要将项目python环境依赖存至本项目下&#xff0c;默认依赖本…

Java14发布!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!​

Java 14 在 2020.3.17 日发布正式版了&#xff0c;但现在很多公司还在使用 Java 7 或 Java 8&#xff0c;每当看到 Java 又发布新版本心里就慌得一匹。不过此版本并不是 LTS (长期支持版) 版本&#xff0c;所以不要慌&#xff0c;我们先来了解一下好了&#xff0c;等 LTS 版本发…

np.copysign_带有Python示例的math.copysign()方法

np.copysignPython math.copysign()方法 (Python math.copysign() method) math.copysign() method is a library method of math module, it is used to get a number with the sign of another number, it accepts two numbers (either integers or floats) and returns a fl…

PyCharm更换pip源为国内源、模块安装、PyCharm依赖包导入导出教程

一、更换pip为国内源 1.使用PyCharm创建一个工程 2.通过File -> Setting…选择解释器为本工程下的Python解释器。 3.单击下图中添加“”&#xff0c; 4.单击下图中的“Manage Repositories”按钮&#xff0c; 6.目前国内靠谱的 pip 镜像源有&#xff1a; - 清华&#xff1…

Java14来了!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!

Java 14 在 2020.3.17 日发布正式版了&#xff0c;但现在很多公司还在使用 Java 7 或 Java 8&#xff0c;每当看到 Java 又发布新版本心里就慌得一匹。不过此版本并不是 LTS (长期支持版) 版本&#xff0c;所以不要慌&#xff0c;我们先来了解一下好了&#xff0c;等 LTS 版本发…

在线批量压缩JPG图片-JpegMini

2019独角兽企业重金招聘Python工程师标准>>> 之前有推荐过一个在线批量压缩PNG图片的网站TinyPng&#xff0c;这儿小觉再次推荐一个同类网站&#xff0c;专门在线批量压缩JPG图片的JpegMini。 当然&#xff0c;大家或者会说现在很多工具或者网站都有提供在线批量压缩…

Python创建目录、判断路径是否为目录、打开文件夹操作

1.Python创建目录 # 导入os模块 import os # 判断一个目录path是否存在 os.path.exists(path) # 创建目录path os.mkdir(path) # 多层创建目录path os.makedirs(path) import ospath E:/test/if os.path.exists(path):pass else:os.mkdir(path)2.判断路径是否为目录 # 导入o…

## c 连接字符_用于字符比较的C#程序

## c 连接字符Input characters and compare them using C# program. 输入字符并使用C&#xff03;程序进行比较。 Prerequisite: Methods to input a single character in C# 先决条件&#xff1a; 在C&#xff03;中输入单个字符的方法 C&#xff03;代码比较两个字符 (C# …