JMH实践-代码性能测试工具
概述
- JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件
- JMH比较典型的应用场景有:
- 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
- 对比接口不同实现在给定条件下的吞吐量;
- 查看多少百分比的请求在多长时间内完成;
基本概念
- 模式
- Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
- AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
- SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
- SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
- Iteration
Iteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。 - Warmup
Warmup 是指在实际进行 benchmark 前先进行预热的行为。为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 benchmark 的结果更加接近真实情况就需要进行预热。
注解说明
- @BenchmarkMode
对应Mode选项,可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍。 - @State
类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。由于JMH允许多线程同时执行测试,不同的选项含义如下:- Scope.Thread:默认的State,每个测试线程分配一个实例;
- Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
- Scope.Group:每个线程组共享一个实例;
- @OutputTimeUnit
benchmark 结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。 - @Benchmark
方法注解,表示该方法是需要进行 benchmark 的对象。 - @Setup
方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。 - @TearDown
方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。 - @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();
}
}
常用选项说明
- include
benchmark 所在的类的名字,这里可以使用正则表达式对所有类进行匹配。 - fork
JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个@Benchmark方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。fork选项也可以通过方法注解以及启动参数来设置。 - warmupIterations
预热的迭代次数,默认1秒。 - measurementIterations
实际测量的迭代次数,默认1秒。 - CompilerControl
可以在@Benchmark注解中指定编译器行为。 - 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 - Level
用于控制 @Setup,@TearDown 的调用时机,默认是 Level.Trial。- Trial:每个benchmark方法前后;
- Iteration:每个benchmark方法每次迭代前后;
- Invocation:每个benchmark方法每次调用前后,谨慎使用,需留意javadoc注释;
- 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微秒之间
JMH实践-代码性能测试工具的更多相关文章
- 利用Docker安装Web前端性能测试工具Sitespeed.io
目录结构 一.Sitespeed.io概述 1.Sitespeed.io简介 2.Sitespeed.io使用场景 二.Sitespeed.io的安装和使用 1.安装Sitespeed.io 2.连接 ...
- 性能测试工具Locust
An open source load testing tool. 一个开源性能测试工具. define user behaviour with python code, and swarm your ...
- 更新整理本人所有博文中提供的代码与工具(C++,2014.01)
为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...
- 更新整理本人所有博文中提供的代码与工具(C++,2013.11)
为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...
- 给CentOS6.3 + PHP5.3 安装PHP性能测试工具 XHProf-0.9.2
一.什么是XHProf XHProf官网:http://pecl.php.net/package/xhprof XHProf是一个分层PHP性能分析工具.它报告函数级别的请求次数和各种指标,包括 阻塞 ...
- 安卓性能测试工具-GT,安测试
GT: 是腾讯出品的一款APP的随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE, Integrated Debug&Test Environment).利用GT,仅凭一部 ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- Ceph性能测试工具和方法。
0. 测试环境 同 Ceph 的基本操作和常见故障排除方法 一文中的测试环境. 1. 测试准备 1.1 磁盘读写性能 1.1.1 单个 OSD 磁盘写性能,大概 165MB/s. root@ceph1 ...
- 更新整理本人所有博文中提供的代码与工具(C++,2013.10)
为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...
随机推荐
- 使用gcc命令编译多个文件
使用g++命令直接一次性编译多个文件 这里以简单的HelloWorld程序为例,假设我们一共有三个文件:main.cpp,HelloWorld.cpp和HelloWorld.h. 其中HelloWor ...
- MongoDB及Mongoose的记录
MongoDB是一种NoSQL的文档型数据库,其存储的文档类型都是JSON对象. 在node.js中由于代码都是异步执行,且nosql也没有“事物”这一定义,所以日常使用中很难保证数据库操作的原子性. ...
- 参加公司工作总结会要准备的内容 IT 技术部
季度总结PPT内容: 1.工作总概述:在总结期内完成的具有代表性的工作内容(最好是直观的实现界面或功能演示截图,而不是苍白的文字描述): 2.问题总结:操作有难度或者难以把握的问题,在和相关人员沟通后 ...
- 泡泡堂BNB[ZJOI2008]
--BZOJ1034 Description 第XXXX届NOI期间,为了加强各省选手之间的交流,组委会决定组织一场省际电子竞技大赛,每一个省的代表队由n名选手组成,比赛的项目是老少咸宜的网络游戏泡泡 ...
- Maven 基本用法
1. 新建一个项目目录 2. 在项目目录中新建并编写 pom.xml 文件, 3. 在项目目录中新建主代码目录 src/main/java 4. 在项目目录中新建测试代码目录 src/test/jav ...
- Docker学习笔记:基础
docker的概念 :docker是一个可供开发者在容器中 开发 部署 运行 应用的一个平台.通过使用Linux容器去部署应用的方式称为容器化. 基础概念 Images and Container i ...
- 优雅的找出ArrayList中重复的元素
https://blog.csdn.net/caoxiaohong1005/article/details/54286384
- MySQL远程连接失败(错误码:2003)
一 环境信息 服务器系统:Ubuntu 18.04 服务器MySQL版本:14.14 Distrib 5.7.25 本地系统:Kali Linux 本地客户端:python3交互模式 本地开发环境:p ...
- 死锁问题------------------------INSERT ... ON DUPLICATE KEY UPDATE*(转)
前言 我们在实际业务场景中,经常会有一个这样的需求,插入某条记录,如果已经存在了则更新它如果更新日期或者某些列上的累加操作等,我们肯定会想到使用INSERT ... ON DUPLICATE K ...
- spring深入学习(二)-----bean的生命周期、IOC容器bean装配
bean的生命周期 1.实例化Bean对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBea ...