概述

  1. JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件
  2. JMH比较典型的应用场景有:
    • 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
    • 对比接口不同实现在给定条件下的吞吐量;
    • 查看多少百分比的请求在多长时间内完成;

基本概念

  1. 模式

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

    Iteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。
  3. Warmup

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

注解说明

  1. @BenchmarkMode

    对应Mode选项,可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍。
  2. @State

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

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

    benchmark 结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。
  4. @Benchmark

    方法注解,表示该方法是需要进行 benchmark 的对象。
  5. @Setup

    方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
  6. @TearDown

    方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。
  7. @Param

    成员注解,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。@Param注解接收一个String数组,在@setup方法执行前转化为为对应的数据类型。多个@Param注解的成员之间是乘积关系,譬如有两个用@Param注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。

@Param注解例子

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class JMHSample_27_Params {
/**
* In many cases, the experiments require walking the configuration space
* for a benchmark. This is needed for additional control, or investigating
* how the workload performance changes with different settings.
*/
@Param({"1", "31", "65", "101", "103"})
public int arg;
@Param({"0", "1", "2", "4", "8", "16", "32"})
public int certainty;
@Benchmark
public boolean bench() {
return BigInteger.valueOf(arg).isProbablePrime(certainty);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_27_Params.class.getSimpleName())
// .param("arg", "41", "42") // Use this to selectively constrain/override parameters
.build();
new Runner(opt).run();
}
}

常用选项说明

  1. include

    benchmark 所在的类的名字,这里可以使用正则表达式对所有类进行匹配。
  2. fork

    JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个@Benchmark方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。fork选项也可以通过方法注解以及启动参数来设置
  3. warmupIterations

    预热的迭代次数,默认1秒。
  4. measurementIterations

    实际测量的迭代次数,默认1秒。
  5. CompilerControl

    可以在@Benchmark注解中指定编译器行为。
  6. Group

    方法注解,可以把多个 benchmark 定义为同一个 group,则它们会被同时执行,譬如用来模拟生产者-消费者读写速度不一致情况下的表现。可以参考如下例子:

    https://github.com/chrishantha/microbenchmarks/blob/v0.0.1-initial-counter-impl/counters/src/main/java/com/github/chrishantha/microbenchmark/counter/CounterBenchmark.java
  7. Level

    用于控制 @Setup,@TearDown 的调用时机,默认是 Level.Trial。

    • Trial:每个benchmark方法前后;
    • Iteration:每个benchmark方法每次迭代前后;
    • Invocation:每个benchmark方法每次调用前后,谨慎使用,需留意javadoc注释;
  8. Threads

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

配置

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.20</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.20</version>
</dependency> //放到plugins中 <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

代码

package com.beikbank.settlement.jms;
import org.openjdk.jmh.annotations.*;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MICROSECONDS) // 输出结果的时间粒度为微秒
@State(Scope.Thread) // 每个测试线程一个实例
public class FirstBenchMark {
private static Logger log = LoggerFactory.getLogger(FirstBenchMark.class); @Benchmark
public String stringConcat() {
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
log.debug(s);
return s;
}
public static void main(String[] args) throws RunnerException {
// 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试
Options opt = new OptionsBuilder().include(FirstBenchMark.class.getSimpleName()).forks(1).warmupIterations(5)
.measurementIterations(5).build();
new Runner(opt).run();
}
}

结果

...
16:05:49.342 [com.beikbank.settlement.jms.FirstBenchMark.stringConcat-jmh-worker-1] DEBUG com.beikbank.settlement.jms.FirstBenchMark - abc
16:05:49.342 [com.beikbank.settlement.jms.FirstBenchMark.stringConcat-jmh-worker-1] DEBUG com.beikbank.settlement.jms.FirstBenchMark - abc
75.525 us/op Result "com.beikbank.settlement.jms.FirstBenchMark.stringConcat":
71.340 ±(99.9%) 12.417 us/op [Average]
(min, avg, max) = (68.386, 71.340, 75.525), stdev = 3.225
CI (99.9%): [58.923, 83.757] (assumes normal distribution) # Run complete. Total time: 00:00:14 Benchmark Mode Cnt Score Error Units
FirstBenchMark.stringConcat avgt 5 71.340 ± 12.417 us/op

可以看出此方法运行时间在71微秒正负12微秒之间

参考

https://blog.csdn.net/lxbjkben/article/details/79410740

JMH实践-代码性能测试工具的更多相关文章

  1. 利用Docker安装Web前端性能测试工具Sitespeed.io

    目录结构 一.Sitespeed.io概述 1.Sitespeed.io简介 2.Sitespeed.io使用场景 二.Sitespeed.io的安装和使用 1.安装Sitespeed.io 2.连接 ...

  2. 性能测试工具Locust

    An open source load testing tool. 一个开源性能测试工具. define user behaviour with python code, and swarm your ...

  3. 更新整理本人所有博文中提供的代码与工具(C++,2014.01)

    为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...

  4. 更新整理本人所有博文中提供的代码与工具(C++,2013.11)

    为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...

  5. 给CentOS6.3 + PHP5.3 安装PHP性能测试工具 XHProf-0.9.2

    一.什么是XHProf XHProf官网:http://pecl.php.net/package/xhprof XHProf是一个分层PHP性能分析工具.它报告函数级别的请求次数和各种指标,包括 阻塞 ...

  6. 安卓性能测试工具-GT,安测试

    GT: 是腾讯出品的一款APP的随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE,  Integrated  Debug&Test  Environment).利用GT,仅凭一部 ...

  7. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  8. Ceph性能测试工具和方法。

    0. 测试环境 同 Ceph 的基本操作和常见故障排除方法 一文中的测试环境. 1. 测试准备 1.1 磁盘读写性能 1.1.1 单个 OSD 磁盘写性能,大概 165MB/s. root@ceph1 ...

  9. 更新整理本人所有博文中提供的代码与工具(C++,2013.10)

    为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...

随机推荐

  1. MySQL数据库和表名大小写敏感开关的打开办法

    背景:window 10系统安装了MySQL57,使用可视化操作工具Navicat Premium 12进行相关操作,一切比较顺利.但在建立数据库和表时,发现大小写命名不敏感.区分大小写输入,保存后默 ...

  2. JavaSE 集合类TreeSet存储自定义对象

    文章目录 一.自动排序功能测试 二.对自定义类的自动排序 一.自动排序功能测试 public class TreeSetDemo { public static void main(String ar ...

  3. angular的json

    在angular从servlet中获取的list数据是字符串格式,需要转为json格式,于是使用语法: $scope.findOne=function(id){ typeTemplateService ...

  4. dubbo入门学习 三 dubbo简介

    http://dubbo.apache.org/zh-cn/docs/user/references/protocol/dubbo.html Dubbo简介 1. Dubbo:一个分布式.高性能.透明 ...

  5. mysql第一课,数据库的简单简单操作方法(配图略虐狗)

    mysql -u root -p 并输入密码进入mysql管理界面 show databases; 显示数据库列表 use 数据库名; 进入该数据库 show tables;显示表列表 建立新表 添加 ...

  6. 20155312 张竞予 Exp 8 Web基础

    Exp 8 Web基础 目录 基础问题回答 (1)什么是表单 (2)浏览器可以解析运行什么语言. (3)WebServer支持哪些动态语言 实践过程记录 1.Web前端:HTML 2.Web前端jav ...

  7. MySql添加远程超级管理员用户

    可以通过发出GRANT语句增加新用户:首先在数据库本机上用ROOT用户登录上MySql,然后运行命令: mysql>GRANT ALL PRIVILEGES ON *.* TO admin'@' ...

  8. T-1-java语言基础

    一.Linux的由来和发展 Linux是开源的操作系统 Linux是服务器端的操作系统 java主要用于服务器端   二.Linux目录结构(与Windows不同) 文件系统不同:Windows是盘符 ...

  9. python实现简单动画——生命游戏

    生命游戏 生命游戏的宇宙是一个无限的,其中细胞的二维正交网格,每个细胞处于两种可能的状态之一,即*活着*或*死亡*(分别是*人口稠密*和*无人居住*).每个细胞与它的八个邻居相互作用,这八个邻居是水平 ...

  10. usb 枚举流程

    Linux-USB总线驱动分析 如下图所示,以windows为例,我们插上一个没有USB设备驱动的USB,就会提示你安装驱动程序 为什么一插上就有会提示信息? 是因为windows自带了USB总线驱动 ...