微基准测试框架JMH
最典型的场景就是你想知道两个功能相同的操作到底哪个性能比较好,通常会自己手撸一段代码,前后增加时间,然后对比多次执行的时间。这种做法比较原始,还要自己处理预热等问题。JMH提供了比较丰富的操作。且看如何使用。
使用示例
这个工具是JDK9 加入的,但是也可以直接使用Maven添加对应的依赖。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
依赖添加完成以后即可编写性能测试程序
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
public class JmhApplication {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JmhApplication.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(5)
.build();
new Runner(opt).run();
}
@Benchmark
public void stringAdd(){
String a = "";
for (int i = 0; i < 10; i++) {
a += i;
}
}
@Benchmark
public void stringBuilderAdd(){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
}
}
如上述代码只需要在要进行基准测试的方法上增加Benchmark注解,即可进行基准测试。而类上的注解直接定义了基准测试的一些全局设置,如测试类型,时间单位等。
main方法里面使用Options类定义的预热运行测试方法的次数,以及运行基准测试运行方法的次数。接着构建Runner类运行基准测试。
// .... 省略其他输出
# Run progress: 50.00% complete, ETA 00:01:11
# Fork: 1 of 1
# Warmup Iteration 1: 23.463 ops/us
# Warmup Iteration 2: 19.687 ops/us
Iteration 1: 18.304 ops/us
Iteration 2: 20.687 ops/us
Iteration 3: 20.641 ops/us
Iteration 4: 20.194 ops/us
Iteration 5: 20.112 ops/us
// .... 省略其他输出
Benchmark Mode Cnt Score Error Units
JmhApplication.stringAdd thrpt 5 5.711 ± 1.784 ops/us
JmhApplication.stringBuilderAdd thrpt 5 19.988 ± 3.757 ops/us
结果如上,可以看出进行了2次的预热,执行5次迭代,来计算结果。最终输出了Benchmark的结果。关注点是Score和对应的单位Units,表示每个时间单位执行的操作次数。即第一个方法每微秒执行5.711 ± 1.784次,第二个方法是每微秒执行19.988 ± 3.757 次。
基准测试类型
基准测试类型,由注解BenchmarkMode来指定,主要由五种选择,实际是4种。
Throughput 整体吞吐量,结果是单位时间的执行次数。
Benchmark Mode Cnt Score Error Units
JmhApplication.stringAdd thrpt 5 5.711 ± 1.784 ops/us
JmhApplication.stringBuilderAdd thrpt 5 19.988 ± 3.757 ops/us
AverageTime 调用的平均时间,结果是执行一次所需要的时间。
Benchmark Mode Cnt Score Error Units
JmhApplication.stringAdd avgt 5 0.150 ± 0.002 us/op
JmhApplication.stringBuilderAdd avgt 5 0.049 ± 0.005 us/op
SampleTime 随机取样,最后输出取样结果的分布
Benchmark Mode Cnt Score Error Units
stringAdd sample 1264158 0.202 ± 0.019 us/op
stringAdd:stringAdd·p0.00 sample 0.100 us/op
stringAdd:stringAdd·p0.50 sample 0.200 us/op
stringAdd:stringAdd·p0.90 sample 0.200 us/op
stringAdd:stringAdd·p0.95 sample 0.200 us/op
stringAdd:stringAdd·p0.99 sample 0.300 us/op
stringAdd:stringAdd·p0.999 sample 2.200 us/op
stringAdd:stringAdd·p0.9999 sample 31.907 us/op
stringAdd:stringAdd·p1.00 sample 3895.296 us/op
stringBuilderAdd sample 1968851 0.082 ± 0.008 us/op
stringBuilderAdd:stringBuilderAdd·p0.00 sample ≈ 0 us/op
stringBuilderAdd:stringBuilderAdd·p0.50 sample 0.100 us/op
stringBuilderAdd:stringBuilderAdd·p0.90 sample 0.100 us/op
stringBuilderAdd:stringBuilderAdd·p0.95 sample 0.100 us/op
stringBuilderAdd:stringBuilderAdd·p0.99 sample 0.100 us/op
stringBuilderAdd:stringBuilderAdd·p0.999 sample 0.300 us/op
stringBuilderAdd:stringBuilderAdd·p0.9999 sample 10.896 us/op
stringBuilderAdd:stringBuilderAdd·p1.00 sample 3563.520 us/op
SingleShotTime 以上模式都是默认一次 iteration 是 1秒,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
// 预热修改为0,执行的次数修改为1
Options opt = new OptionsBuilder()
.include(JmhApplication.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.measurementIterations(1)
.build();
// 结果如下
Benchmark Mode Cnt Score Error Units
JmhApplication.stringAdd ss 32612.100 us/op
JmhApplication.stringBuilderAdd ss 18.500 us/op
All 运行全部以上的基准测试模式
其中最常用的是Throughput和AverageTime模式,其他的在正常场景基本上可以略过。
后续
工具使用起来比较方便,功能也是挺多,此处不逐个介绍。一个很大的缺点是文档比较少,官方有完整的示例代码,可以直接阅读和执行来学习如何使用。
- Mac OS平台下应用程序安装包制作工具Packages的使用介绍
- 协议森林02 小喇叭开始广播 (以太网与WiFi协议)
- 信号与频谱
- Mac OS平台下应用程序安装包制作工具Packages的使用介绍(补充)
- 数字按照不同格式转换成字符串
- macOS下加载动态库dylib报"code signature invalid"错误的解决办法
- Asp.net管道模型(管线模型)之一发不可收拾
- Python深入01 特殊方法与多范式
- MacOS平台下@rpath在动态链接库中的应用
- Python深入02 上下文管理器
- 剑指OFFER之字符串的排列(九度OJ1369)
- 高性能计算机传奇
- ffmpeg编解码视频导致噪声增大的一种解决方法
- 剑指OFFER之二叉搜索树与双向链表(九度OJ1503)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 根据 PID 获取 K8S Pod名称 - 反之 POD名称 获取 PID
- 用python实现一个verilog网表Parser
- 经典 | Python实例小挑战—Part eight
- python的数字与字符串相互转换
- 坑!url中含有中文导致nginx 400。锅是tomcat的
- pytest文档43-元数据使用(pytest-metadata)
- Python | 面试的常客,经典的生产消费者模式
- LeetCode 87,因为题目晦涩而被点了1500+反对的搜索问题
- LeetCode 87,远看是字符串其实是搜索,你能做出来吗?
- Golang中的interface是干嘛的?从面向对象中的多态与接口开始说起……
- pandas | DataFrame基础运算以及空值填充
- C++设计模式笔记(08) - Factory Method工厂方法
- 【Code】GraphSAGE 源码解析
- Kafka常见的导致重复消费原因和解决方案
- 近30个MySQL常用函数,必须推荐!