测试工具之JMH详解

文章目录

  • 1 JMH
    • 1.1 引言
    • 1.2 简介
    • 1.3 DEMO演示
      • 1.3.1 测试项目构建
      • 1.3.2 编写性能测试
      • 1.3.3 执行测试
      • 1.3.4 报告结果
    • 1.4 注解介绍
      • 1.4.1 @BenchmarkMode
      • 1.4.2 @Warmup
      • 1.4.3 @Measurement
      • 1.4.4 @Threads
      • 1.4.5 @Fork
      • 1.4.6 @OutputTimeUnit
      • 1.4.7 @Benchmark
      • 1.4.8 @Param
      • 1.4.9 @Setup
      • 1.4.10 @TearDown
      • 1.4.11 @State
    • 1.5 启动方法解释

1 JMH

1.1 引言

在日常开发中,我们对一些代码的调用或者工具的使用会存在多种选择方式,在不确定他们性能的时候,我们首先想要做的就是去测量它。大多数时候,我们会简单的采用多次计数的方式来测量,来看这个方法的总耗时。

但是,如果熟悉 JVM 类加载机制的话,应该知道 JVM 默认的执行模式是 JIT 编译与解释混合执行。

JVM 通过热点代码统计分析,识别高频方法的调用、循环体、公共模块等,基于 JIT 动态编译技术,会将热点代码转换成机器码,直接交给 CPU 执行。
在这里插入图片描述
也就是说,JVM 会不断的进行编译优化,这就使得很难确定重复多少次才能得到一个稳定的测试结果?所以,很多有经验的同学会在测试代码前写一段预热的逻辑。

1.2 简介

JMH,全称 Java Microbenchmark Harness(微基准测试框架),是专门用于 Java 代码微基准测试的一套测试工具 API,是由 OpenJDK/Oracle 官方发布的工具。

何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。

Java 的基准测试需要注意的几个点:

  • 测试前需要预热
  • 防止无用代码进入测试方法中
  • 并发测试
  • 测试结果呈现

JMH 的使用场景:

  • 定量分析某个热点函数的优化效果
  • 想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性
  • 对比一个函数的多种实现方式

1.3 DEMO演示

1.3.1 测试项目构建

JMH 是内置 Java9 及之后的版本。这里是以 Java17 进行说明。为了方便,这里直接介绍使用 maven 构建 JMH 测试项目的方式

第一种是使用命令行构建,在指定目录下执行以下命令:

$ mvn archetype:generate \
          -DinteractiveMode=false \
          -DarchetypeGroupId=org.openjdk.jmh \
          -DarchetypeArtifactId=jmh-java-benchmark-archetype \
          -DgroupId=org.sample \
          -DartifactId=test \
          -Dversion=1.0

对应目录下会出现一个 test 项目,打开项目后我们会看到这样的项目结构。
在这里插入图片描述

第二种方式就是直接在现有的 maven 项目中添加 jmh-corejmh-generator-annprocess 的依赖来集成 JMH。

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
    <scope>provided</scope>
</dependency>

1.3.2 编写性能测试

这里我以测试 LinkedList 通过 index 方式迭代和 foreach 方式迭代的性能差距为例子,编写测试类

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(Threads.MAX)
public class TestList{
    private static final int SIZE = 10000;

    private List<String> list = new LinkedList<>();

    @Setup
    public void setUp() {
        for (int i = 0; i < SIZE; i++) {
            list.add(String.valueOf(i));
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void forIndexIterate() {
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
            System.out.print("");
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void forEachIterate() {
        for (String s : list) {
            System.out.print("");
        }
    }
}

1.3.3 执行测试

运行 JMH 基准测试有两种方式,一个是生产 jar 文件运行,另一个是直接写 main 函数或者放在单元测试中执行。

生成 jar 文件的形式主要是针对一些比较大的测试,可能对机器性能或者真实环境模拟有一些需求,需要将测试方法写好了放在 linux 环境执行。
具体命令如下:

$ mvn clean install
$ java -jar target/benchmarks.jar

我们日常中遇到的一般是一些小测试,比如我上面写的例子,直接在 IDE 中跑就好了。
启动方式如下:

public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(LinkedListIterationBenchMark.class.getSimpleName())
                .forks(1)
                .warmupIterations(2)
                .measurementIterations(2)
                .output("E:/Benchmark.log")
                .build();

        new Runner(opt).run();
    }

1.3.4 报告结果

最后的输出结果如下:

Benchmark                  Mode  Cnt    Score   Error  Units
TestList.forEachIterate   thrpt    2  590.884          ops/s
TestList.forIndexIterate  thrpt    2  179.808          ops/s

整个文档:

# Detecting actual CPU count: 8 detected
# JMH version: 1.35
# VM version: JDK 17.0.6, Java HotSpot(TM) 64-Bit Server VM, 17.0.6+9-LTS-190
# VM invoker: D:\SoftWare\Environments\jdk-17.0.6\bin\java.exe
# VM options: -javaagent:D:\SoftWare\Codes\Idea\IntelliJ IDEA 2022.1.4\lib\idea_rt.jar=54795:D:\SoftWare\Codes\Idea\IntelliJ IDEA 2022.1.4\bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: cn.test.TestList.forEachIterate

# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 1
# Warmup Iteration   1: 587.183 ops/s
# Warmup Iteration   2: 606.869 ops/s
Iteration   1: 582.747 ops/s
Iteration   2: 599.022 ops/s


Result "cn.test.TestList.forEachIterate":
  590.884 ops/s


# JMH version: 1.35
# VM version: JDK 17.0.6, Java HotSpot(TM) 64-Bit Server VM, 17.0.6+9-LTS-190
# VM invoker: D:\SoftWare\Environments\jdk-17.0.6\bin\java.exe
# VM options: -javaagent:D:\SoftWare\Codes\Idea\IntelliJ IDEA 2022.1.4\lib\idea_rt.jar=54795:D:\SoftWare\Codes\Idea\IntelliJ IDEA 2022.1.4\bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 2 iterations, 10 s each
# Measurement: 2 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: cn.test.TestList.forIndexIterate

# Run progress: 50.00% complete, ETA 00:00:41
# Fork: 1 of 1
# Warmup Iteration   1: 178.333 ops/s
# Warmup Iteration   2: 178.797 ops/s
Iteration   1: 180.050 ops/s
Iteration   2: 179.565 ops/s


Result "cn.test.TestList.forIndexIterate":
  179.808 ops/s


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

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.

NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.

Benchmark                  Mode  Cnt    Score   Error  Units
TestList.forEachIterate   thrpt    2  590.884          ops/s
TestList.forIndexIterate  thrpt    2  179.808          ops/s

1.4 注解介绍

下面我们来详细介绍一下相关的注解

1.4.1 @BenchmarkMode

微基准测试类型。JMH 提供了以下几种类型进行支持:

类型描述
Throughput每段时间执行的次数,一般是秒
AverageTime平均时间,每次操作的平均耗时
SampleTime在测试中,随机进行采样执行的时间
SingleShotTime在每次执行中计算耗时
All所有模式

可以注释在方法级别,也可以注释在类级别

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

1.4.2 @Warmup

这个单词的意思就是预热iterations = 3 就是指预热轮数

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Warmup(iterations = 3)
public void m() {
    ...
}

1.4.3 @Measurement

正式度量计算的轮数:

  • iterations:进行测试的轮次
  • time:每轮进行的时长
  • timeUnit:时长单位
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Measurement(iterations = 3)
public void m() {
    ...
}

1.4.4 @Threads

每个进程中的测试线程

@Threads(Threads.MAX)
public class LinkedListIterationBenchMark {
    ...
}

1.4.5 @Fork

进行 fork 的次数。如果 fork 数是 3 的话,则 JMH 会 fork 出 3 个进程来进行测试

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})
@Fork(value = 3)
public void m() {
    ...
}

1.4.6 @OutputTimeUnit

基准测试结果的时间类型。一般选择秒、毫秒、微秒

@OutputTimeUnit(TimeUnit.SECONDS)
public class LinkedListIterationBenchMark {
    ...
}

1.4.7 @Benchmark

方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit@Test 类似

1.4.8 @Param

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

1.4.9 @Setup

方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的

1.4.10 @TearDown

方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。

1.4.11 @State

当使用 @Setup 参数的时候,必须在类上加这个参数,不然会提示无法运行。就比如我上面的例子中,就必须设置 state

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

其中的参数Scope 主要分为三种:

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

1.5 启动方法解释

在启动方法中,可以直接指定上述说到的一些参数,并且能将测试结果输出到指定文件中。

/**
* 仅限于IDE中运行
* 命令行模式 则是 build 然后 java -jar 启动
*
* 1. 这是benchmark 启动的入口
* 2. 这里同时还完成了JMH测试的一些配置工作
* 3. 默认场景下,JMH会去找寻标注了@Benchmark的方法,可以通过include和exclude两个方法来完成包含以及排除的语义
*/
public static void main(String[] args) throws RunnerException {
   Options opt = new OptionsBuilder()
           // 包含语义
           // 可以用方法名,也可以用XXX.class.getSimpleName()
           .include("Helloworld")
           // 排除语义
           .exclude("Pref")
           // 预热10轮
           .warmupIterations(10)
           // 代表正式计量测试做10轮,
           // 而每次都是先执行完预热再执行正式计量,
           // 内容都是调用标注了@Benchmark的代码。
           .measurementIterations(10)
           //  forks(3)指的是做3轮测试,
           // 因为一次测试无法有效的代表结果,
           // 所以通过3轮测试较为全面的测试,
           // 而每一轮都是先预热,再正式计量。
           .forks(3)
           .output("E:/Benchmark.log")
           .build();

   new Runner(opt).run();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 812. 最大三角形面积

题目 给你一个由 X-Y 平面上的点组成的数组 points &#xff0c;其中 points[i] [xi, yi] 。从其中取任意三个不同的点组成三角形&#xff0c;返回能组成的最大三角形的面积。与真实值误差在 10-5 内的答案将会视为正确答案。 示例 1&#xff1a; 输入&#xff1a;points [[…

ActiveReportsJS 4.0 FIX ActiveReportsJS 4.0 Crack

JavaScript 报告工具是一组用于数据整合和可视化的 Web 组件。ActiveReportsJS 是前端开发人员用来在 Web 应用程序中嵌入报告的解决方案。报表设计器和查看器组件、强大的数据可视化器和丰富的 API 等主要功能使 ActiveReportsJS 成为行业领导者。 JavaScript 报告引擎 利用强…

Spark SQL join操作详解

一、 数据准备 本文主要介绍 Spark SQL 的多表连接&#xff0c;需要预先准备测试数据。分别创建员工和部门的 Datafame&#xff0c;并注册为临时视图&#xff0c;代码如下&#xff1a; val spark SparkSession.builder().appName("aggregations").master("lo…

Python进阶内容--迭代器和生成器

什么是迭代器 在 Python 中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一个访问集合元素的对象&#xff0c;它能够实现遍历集合的所有元素&#xff0c;而无需了解集合底层结构和细节。Python 中所有可迭代的对象&#xff08;如 列表、元组、字符串、字典、集合等&a…

leetcodeTmp

文章目录 39. 组合总和33. 搜索旋转排序数组153. 寻找旋转排序数组中的最小值49. 字母异位词分组53. 最大子数组和55. 跳跃游戏56. 合并区间62. 不同路径 39. 组合总和 39. 组合总和 DFS排列&#xff1a;每个元素可选0次&#xff0c;1次以及多次 public List<List<Int…

元宇宙:虚拟仿真技术的全面提升

在当今数字化的世界中&#xff0c;我们经常听到虚拟现实、增强现实、混合现实等技术的名词&#xff0c;这些技术的应用越来越成熟。其中&#xff0c;虚拟仿真技术是一种通过计算机技术来模拟实际场景和对象的过程&#xff0c;它为我们提供了更多的可能性。而最近备受瞩目的元宇…

加密的本质:数学的不对称性

文章目录 引言I 预备知识1.1 加密和授权1.2 非对称的特性II 椭圆曲线加密的方法2.1 椭圆曲线2.2 椭圆曲线的性质引言 不对称有时却自有其妙处与美感,比如黄金分割就是不对称的。 可以通过加密和授权,兼顾保护信息不外泄,而且某些得到授权的人还能使用信息。 I 预备知识 …

亚马逊云科技为全球的可持续发展进程做出贡献

可持续发展是一个涉及经济、环境和社会三个方面的复杂问题。经济发展必须在保护环境和社会公正的前提下进行&#xff0c;这样才能实现真正的可持续发展。为了实现这一目标&#xff0c;人们需要借助技术手段&#xff0c;更好地理解和解决环境和社会问题。 亚马逊云科技是全球领…

(大数据开发随笔9)Hadoop 3.3.x分布式环境部署——全分布式模式

索引 完全分布式模式守护进程布局集群搭建准备总纲配置文件格式化集群启动集群 集群控制命令集群启停进程查看启动日志查看集群常见问题 案例演示&#xff1a;WordCount 完全分布式模式 分布式文件系统中&#xff0c;HDFS相关的守护进程也分布在不同的机器上&#xff0c;如&am…

tp5实现导入excel表到数据库

hello&#xff0c;大家好&#xff0c;好长时间没有更新文章了。最近一直在忙着做项目。所以断更了。 那么好&#xff0c;各位老铁是否想要实现导入导出的功能 请关注我&#xff0c;解密如何实现导入导出&#xff0c; 那么今天先来讲一下用thinkphp5.0 如何实现Excel表格导入数据…

js 事件流程

描述 JavaScript 的执行是单线程的&#xff0c;后面的任务需要等待前面的任务完全完成后&#xff0c;再去执行。DOM 事件&#xff08;文件的加载等&#xff09;、定时器、网络请求等事件&#xff0c;并不会消耗 CPU&#xff0c;这些事件无需等候&#xff0c;所以出现了异步。主…

【Unity VR开发】结合VRTK4.0:创建一个按钮(Option Button)

语录&#xff1a; 如同天上降魔主&#xff0c;真是人间太岁神。 前言&#xff1a; 选项按钮是一种提供多项选择选项的方法&#xff0c;其中只有一个按钮可以处于激活状态&#xff0c;激活另一个按钮时将确保组中的所有其他按钮都已停用。我们可以使用嵌套在预制件中的预制件来实…

C++命名空间域namespace与域作用限制符: :,cin,cout输入输出简单介绍

TIPS C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等C总计63个关键字&#xff0c;C语言32个关键字&#xff0c;具体没有必要先不去管它 域&#xff0c;命名空间域与namespace关键字 cpp需要解决的第一…

数据库中的视图及三级模式结构

文章目录 一、视图二、数据库三级模式结构 一、视图 简单地说&#xff0c;视图可以看成是一个窗口&#xff0c;它所反映的是一个表或若干表的局部数据&#xff0c;可以简化查询语句。视图一经定义&#xff0c;用户就可以把它当作表一样来查询数据。 但视图和基本表不同&#…

400以内的蓝牙耳机哪款好?400以内蓝牙耳机排行榜

谈起TWS&#xff0c;无论是传统的音频厂商还是手机厂商&#xff0c;都是其不可或缺的重要产品线&#xff0c;现在很多许多蓝牙耳机都不是千篇一律得形状&#xff0c;市场也鲜有商家在外观上下功夫&#xff0c;下面分享几款400元以内&#xff0c;内外兼具的耳机品牌。 一、南卡…

Pytorch实现图像风格迁移(一)

图像风格迁移是图像纹理迁移研究的进一步拓展&#xff0c;可以理解为针对一张风格图像和一张内容图像&#xff0c;通过将风格图像的风格添加到内容图像上&#xff0c;从而对内容图像进行进一步创作&#xff0c;获得具有不同风格的目标图像。基于深度学习网络的图像风格迁移主要…

LeetCode热题HOT100:76. 最小覆盖子串,84.柱状图中最大的矩形、96. 不同的二叉搜索树

LeetCode 热题 HOT 100 76. 最小覆盖子串 题目&#xff1a;给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字…

ADManager Plus:简化 Active Directory 管理的完美工具

在企业中&#xff0c;Active Directory&#xff08;AD&#xff09;是一个非常重要的组件&#xff0c;用于管理和控制所有计算机和用户的访问权限。然而&#xff0c;AD的管理和维护需要一定的技术能力和时间成本。为了简化这个过程&#xff0c;ManageEngine 推出了 ADManager Pl…

Leetcode-二叉树

1.中序-后序构建二叉树 106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 1. 首先根据后序&#xff08;左右中&#xff09;确定顶点元素&#xff1b; 2. 根据顶点元素划分中序序列&#xff1b; 3. 根据划分中序序列中-左子树的长度&#xff0c;进…

数据类型及变量的定义、使用和注意事项

数据类型 计算机存储单元 变量的定义格式&#xff1a; 数据类型 变量名数据值; 我们知道计算机是可以用来存储数据的&#xff0c;但是无论是内存还是硬盘&#xff0c;计算机存储设备的最小信息单元叫“位( bit ) "&#xff0c;我们又称之为“比特位”&#xff0c;通常用…