JMH – Java基准测试
官方资源
应用场景
- 对要使用的数据结构不确定,不知道谁的性能更好
- 对历史方法代码重构,要评判改造之后的性能提升多少 (我要做的场景)
- 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
- 对比接口不同实现在给定条件下的吞吐量
- 查看多少百分比的请求在多长时间内完成
背景限制(防杠指南)
- 业务场景?
因为当前项目是接手比较老的项目,已经有成熟业务在跑,原先的生成模型是nextByCalendarAndRandom
, 序号生成是采用两位随机数,然后随机数产生了冲突,一毫秒内产生的两个随机数有冲突, - 为什么不直接使用 snowflake?
原先的生成逻辑 6(商户号) + 15(yyMMddHHmmssSSS 最大长度,可能比15小) + 2(随机数) = 23 (最大长度)
如果使用雪花算法,则 6 + 19 = 25 (最大长度),且现在业务方较多,不确定对方是否有限制该字段长度,再就是如果对雪花算法进行裁剪,也不能保证肯定不会出现冲突,经衡量过后,暂时不使用雪花算法,后续业务方能确定长度没有问题,就可以升级 - 这个算法不是分布式的,如果是两台服务器,则出现冲突的可能性就变大了
是的,如果两台服务同时运行,然后又同时有请求进来,就有很大的可能性出现冲突,但现在的业务状况是单体架构,只不过做了主备服务,主服务宕机,备份才会启动,暂时不会两台服务同时启动 - 那如果采用
nextByCalendarAndAtomicInteger
自增,就表示一毫秒最大只有100个请求能进来?超过就肯定会冲突?
是的,这个也是业务决定的,如果我们当前的业务量超过每毫秒超100,那问题可能不是我这里的冲突了,服务会率先被压垮 - 最终的业务采用什么方法?
使用了nextByLocalDateTimeAndAtomicInteger
方法,也有每毫秒超100必定重复的限制
引用依赖
<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>
</dependency>
测试代码
@UtilityClass
public class IdWork {
@Deprecated
public static String nextByCalendarAndRandom(String merchantNo) {
Calendar now = Calendar.getInstance();
long random1 = Math.round(Math.random() * 9);
long random2 = Math.round(Math.random() * 9);
String timestamp = (now.get(Calendar.YEAR) + "").substring(2)
+ (now.get(Calendar.MONTH) + 1)
+ now.get(Calendar.DAY_OF_MONTH)
+ now.get(Calendar.HOUR_OF_DAY)
+ now.get(Calendar.MINUTE)
+ now.get(Calendar.SECOND)
+ now.get(Calendar.MILLISECOND);
return merchantNo + timestamp + random1 + random2;
}
@Deprecated
public static String nextByLocalDateTimeAndRandom(String merchantNo) {
LocalDateTime now = LocalDateTime.now();
long random1 = Math.round(Math.random() * 9);
long random2 = Math.round(Math.random() * 9);
String timestamp = (now.getYear() + "").substring(2)
+ now.getMonthValue()
+ now.getDayOfMonth()
+ now.getHour()
+ now.getMinute()
+ now.getSecond()
+ (now.getNano() / 1000000);
return merchantNo + timestamp + random1 + random2;
}
@Deprecated
public static String nextByCalendarAndAtomicInteger(String merchantNo) {
Calendar now = Calendar.getInstance();
String timestamp = (now.get(Calendar.YEAR) + "").substring(2)
+ (now.get(Calendar.MONTH) + 1)
+ now.get(Calendar.DAY_OF_MONTH)
+ now.get(Calendar.HOUR_OF_DAY)
+ now.get(Calendar.MINUTE)
+ now.get(Calendar.SECOND)
+ now.get(Calendar.MILLISECOND);
return merchantNo + timestamp + getSeqNo();
}
@Deprecated
public static String nextByLocalDateTimeAndAtomicInteger(String merchantNo) {
LocalDateTime now = LocalDateTime.now();
String timestamp = (now.getYear() + "").substring(2)
+ now.getMonthValue()
+ now.getDayOfMonth()
+ now.getHour()
+ now.getMinute()
+ now.getSecond()
+ (now.getNano() / 1000000);
return merchantNo + timestamp + getSeqNo();
}
public static String nextBySnowflake(String merchantNo) {
return merchantNo + IdGenerator.next();
}
private static AtomicInteger seqNo = new AtomicInteger(1);
private static String getSeqNo() {
int curSeqNo = seqNo.getAndIncrement();
if (curSeqNo > 99) { // 重置,也可以取模
seqNo = new AtomicInteger(1);
}
if (curSeqNo < 10) {
return "0" + curSeqNo;
}
return curSeqNo + "";
}
public static void main(String[] args) {
String next1 = IdWork.nextByCalendarAndRandom("900087");
System.out.println(next1);
String next2 = IdWork.nextByLocalDateTimeAndRandom("900087");
System.out.println(next2);
String next3 = IdWork.nextByCalendarAndAtomicInteger("900087");
System.out.println(next3);
String next4 = IdWork.nextByLocalDateTimeAndAtomicInteger("900087");
System.out.println(next4);
String next5 = IdWork.nextBySnowflake("900087");
System.out.println(next5);
}
}
public class IdTest {
@Benchmark
public String getIdBySnowflake() {
return IdWork.nextBySnowflake("900087");
}
@Benchmark
public String nextByCalendarAndRandom() {
return IdWork.nextByCalendarAndRandom("900087");
}
@Benchmark
public String nextByLocalDateTimeAndRandom() {
return IdWork.nextByLocalDateTimeAndRandom("900087");
}
@Benchmark
public String nextByCalendarAndAtomicInteger() {
return IdWork.nextByCalendarAndAtomicInteger("900087");
}
@Benchmark
public String nextByLocalDateTimeAndAtomicInteger() {
return IdWork.nextByLocalDateTimeAndAtomicInteger("900087");
}
public static void main(String[] args) throws RunnerException {
// 吞吐量
// Options opt = new OptionsBuilder()
// .include(IdTest.class.getSimpleName())
// .mode(Mode.Throughput)
// .forks(1)
// .build();
// 平均耗时
Options opt = new OptionsBuilder()
.include(IdTest.class.getSimpleName())
.mode(Mode.AverageTime)
.timeUnit(TimeUnit.NANOSECONDS)
.forks(1)
.build();
new Runner(opt).run();
}
// 吞吐量
// Benchmark Mode Cnt Score Error Units
// IdTest.getIdBySnowflake thrpt 5 4070403.840 ± 11302.832 ops/s
// IdTest.nextByCalendarAndAtomicInteger thrpt 5 4201822.821 ± 177869.095 ops/s
// IdTest.nextByCalendarAndRandom thrpt 5 4085723.001 ± 47505.309 ops/s
// IdTest.nextByLocalDateTimeAndAtomicInteger thrpt 5 5036852.390 ± 153313.836 ops/s
// IdTest.nextByLocalDateTimeAndRandom thrpt 5 5199148.189 ± 405132.888 ops/s
// 平均耗时
// Benchmark Mode Cnt Score Error Units
// IdTest.getIdBySnowflake avgt 5 245.739 ± 0.302 ns/op
// IdTest.nextByCalendarAndAtomicInteger avgt 5 239.174 ± 4.244 ns/op
// IdTest.nextByCalendarAndRandom avgt 5 251.084 ± 5.798 ns/op
// IdTest.nextByLocalDateTimeAndAtomicInteger avgt 5 197.332 ± 0.779 ns/op
// IdTest.nextByLocalDateTimeAndRandom avgt 5 212.105 ± 1.888 ns/op
}
概念理解
类型 | 作用域 | 描述 | 备注 |
---|---|---|---|
Benchmark | ElementType.METHOD | 最重要的注解,标记需要执行的方法 | |
BenchmarkMode | ElementType.METHOD, ElementType.TYPE | 统计的维度,有吞吐量,平均耗时,也可以组合使用 | |
Fork | ElementType.METHOD, ElementType.TYPE | 复制多个进程来执行方法,每轮默认Iteration循环5次,如果fork 3,则会执行3*5 次,一般默认值1就可以 | |
Measurement | ElementType.METHOD, ElementType.TYPE | 方法控制:循环次数,每次循环时间以及对应的时间单位 | |
Warmup | ElementType.METHOD,ElementType.TYPE | 预热,避免系统冷启动导致的性能测试不准 | |
OutputTimeUnit | ElementType.METHOD, ElementType.TYPE | 输出时间单位,默认是秒 | |
Param | ElementType.FIELD | 可以指定遍历参数,针对特殊字段测试不同的性能 | |
Setup | ElementType.METHOD | 启动类设置,类似 junit Before类型注解 | |
TearDown | ElementType.METHOD | 销毁类设置,类似junit After类型注解,一般用于销毁池化的资源 | |
Threads | ElementType.METHOD,ElementType.TYPE | ||
Timeout | ElementType.METHOD,ElementType.TYPE | ||
AuxCounters | ElementType.TYPE | 辅助计数器,可以统计 @State 修饰的对象中的 public 属性被执行的情况 | |
Group | ElementType.METHOD | ||
GroupThreads | ElementType.METHOD | ||
CompilerControl | ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE | 内联扩展是一种特别的用于消除调用函数时所造成的固有时间消耗方法,这里用来控制方法或类是否内联 | |
OperationsPerInvocation | ElementType.METHOD, ElementType.TYPE |
BenchmarkMode 执行模式(可以多个组合执行)
类型 | 描述 |
---|---|
Throughput | 每段时间执行的次数,一般是秒 |
AverageTime | 平均时间,每次操作的平均耗时 |
SampleTime | 在测试中,随机进行采样执行的时间 |
SingleShotTime | 在每次执行中计算耗时 |
All | 所有模式 |
// 常用的注解
@BenchmarkMode({Mode.Throughput,Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class BenchmarkTest {
@Benchmark
public long test() {}
}
// 使用 OptionsBuilder 建造者模式构建 Options, 然后在main方法执行,建议使用
Options opt = new OptionsBuilder()
.include(IdTest.class.getSimpleName())
.mode(Mode.AverageTime)
.mode(Mode.Throughput)
.timeUnit(TimeUnit.NANOSECONDS)
.warmupIterations(3)
.warmupTime(TimeValue.seconds(1))
.measurementIterations(5)
.measurementTime(TimeValue.seconds(1))
.forks(1)
.build();
一些提示
避免循环
JVM会对循环进行优化,这样会导致获取的测试结果不准确。
引用资源
jmh-java-microbenchmark-harness
jenkov: java-performance
jmh-benchmark-with-examples
Java基准测试工具 —— JMH使用指南
JMH – Java基准测试的更多相关文章
- JMH java基准测试
Measure, don’t guess! JMH适用场景 JMH只适合细粒度的方法测试 原理 编译时会生成一些测试代码,一般都会继承你的类 maven依赖 <dependencies> ...
- 健壮的 Java 基准测试
健壮的 Java 基准测试 健壮的 Java 基准测试,第 1 部分: 问题 了解 Java 代码基准测试的问题 Brent Boyer, 程序员, Elliptic Group, Inc. 简介:程 ...
- Micro Benchmark Framework java 基准测试类库
Micro Benchmark Framework 框架主要是method 层面上的 benchmark,精度可以精确到微秒级 比较典型的使用场景还有: 想定量地知道某个函数需要执行多长时间,以及执行 ...
- jmh 微基准测试
选择依据:对某段代码的性能测试. 1.运行方法 mvn clean install java -jar target/benchmarks.jar JMHSample_02 -f 1 2.maven ...
- JMH基准测试框架
jmh-gradle-plugin, 集成JMH基准测试框架和 Gradle 0 赞 0 评论 文章标签:Gradle JMH 基准 INT benchmark framework 帧 ...
- JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握!
Java 性能测试难题 现在的 JVM 已经越来越为智能,它可以在编译阶段.加载阶段.运行阶段对代码进行优化.比如你写了一段不怎么聪明的代码,到了 JVM 这里,它发现几处可以优化的地方,就顺手帮你优 ...
- Java基准性能测试--JMH使用介绍
JMH是什么 JMH是Java Microbenchmark Harness的简称,一个针对Java做基准测试的工具,是由开发JVM的那群人开发的.想准确的对一段代码做基准性能测试并不容易,因为JVM ...
- Java监控工具介绍,VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,Java微基准测试
本文是本人前一段时间做一个简单Java监控工具调研总结,主要包括VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,以及对Java微基准测试的简单介绍, ...
- Java监控工具介绍,VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,Java微基准测试【转】
Java监控工具介绍,VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,Java微基准测试[转] 本文是本人前一段时间做一个简单Java监控工具调研总 ...
- [翻译]现代java开发指南 第二部分
现代java开发指南 第二部分 第二部分:部署.监控 & 管理,性能分析和基准测试 第一部分,第二部分 =================== 欢迎来到现代 Java 开发指南第二部分.在第一 ...
随机推荐
- pysimplegui之元素简单介绍(元素值得获取修改,key的规范及特殊用法)
重点 1获取元素的值 Input(key='mykey') values['mykey'] 2通过key查找元素 对象window['key'] 3更新元素的值 window['key'](要更新的值 ...
- 活动预告 | Jax Diffusers 社区冲刺线上分享(还有北京线下活动)
我们的 Jax Diffuser 社区冲刺活动已经截止报名,全球有 200 多名参赛选手成功组成了约 70 支队伍共同参赛. 为了帮助参赛者更好的完成自己的项目,也为了与更多社区成员们分享扩散模型和生 ...
- CommunityToolkit.Mvvm8.1 消息通知(4)
本系列文章导航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址 ...
- 链式描述线性表(C++实现)
在链式描述中,线性表的元素在内存中的存储位置是随机的,每个元素都有一个明确的指针或链指向下一个元素的位置 chain类 在此使用单向链表实现了线性表,其中最后一个节点的指针域为NULL,即它用单向链接 ...
- Django笔记二十八之数据库查询优化汇总
本文首发于公众号:Hunter后端 原文链接:Django笔记二十八之数据库查询优化汇总 这一篇笔记将从以下几个方面来介绍 Django 在查询过程中的一些优化操作,有一些是介绍如何获取 Django ...
- nginx启动报错80端口号已占用
开启或重启Nginx时报如下错误: Nginx [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) 原因是端口号80被其 ...
- 2020-08-27:OpenStack与Docker的区别?
福哥答案2020-08-27: 此答案来自qq群巨佬:openstack是个体系一个完整的方案系统 基于虚拟机 docker只是一个内核上特殊模式跑与其他系统进程隔离的进程的软件方案真正与docker ...
- 2022-08-27:以下go语言代码输出什么?A:[0];B:panic;C:7;D:不清楚。 package main import ( “fmt“ ) func main() { a
2022-08-27:以下go语言代码输出什么?A:[0]:B:panic:C:7:D:不清楚. package main import ( "fmt" ) func main() ...
- 2022-07-28:以下go语言代码输出什么?A:AA;B:AB;C:BA;D:BB。 package main import ( “fmt“ ) func main() { f
2022-07-28:以下go语言代码输出什么?A:AA:B:AB:C:BA:D:BB. package main import ( "fmt" ) func main() { f ...
- 2021-02-08:给定一个字符串str,请问这个字符串的最长回文子序列长度是多少?
2021-02-08:给定一个字符串str,请问这个字符串的最长回文子序列长度是多少? 福哥答案2021-02-08: 自然智慧即可.1.原序列和反序列求公共子序列.无代码.2.递归.有代码.3.动态 ...