JMH 微基准测试(性能测试)

写本文主要是简单记录一下JMH的使用方式。JMH全名是Java Microbenchmark Harness,主要为在jvm上运行的程序进行基准测试的工具。作为一个开发人员,在重构代码,或者确认功能的性能时,可以选中这个工具。
本文场景:代码重构,测试新代码和旧代码的性能区别(QPS)

准备工作
●JMH官方使用文档:OpenJDK: jmh
●【推荐】JMH GitHub地址(包含示例代码):https://github.com/openjdk/jmh
●IntelliJ(2020.2.3 社区版)
●Intellij 安装插件 JMH Java Microbenchmark Harness

关键参数介绍
测试程序注解介绍
●BenchmarkMode:基准模式
○参数:value
■Mode.Throughput:单位时间吞吐量(ops)
■Mode.AverageTime:每次操作的平均时间
■Mode.SampleTime:采样每个操作的时间
■Mode.SingleShotTime:测量一次操作的时间
■Mode.All:把上述的都列出来
●Warmup:预热。在测试代码运行前运行,主要防止 程序初始化 或 jvm运行一段时间后自动优化代码 产生的影响。
○参数如下:
■iterations:运行次数,默认:-1
■time:每次运行的时间,默认:-1
■timeUnit:运行时间的单位,默认:秒
■batchSize:批处理大小,每次操作调用几次方法,默认:-1
●Measurement:具体测试参数。同 Warmup
●Threads:每个进程中的测试线程,可用于类或者方法上。一般选择为cpu乘以2。如果配置了 Threads.MAX ,代表使用 Runtime.getRuntime().availableProcessors() 个线程。
●Fork:
○参数如下:
■value参数:多少个进程来测试,如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试
●State:状态共享范围。
○参数如下:
■Scope.Thread:不和其他线程共享
■Scope.Group:相同类型的所有实例将在同一组内的所有线程之间共享。每个线程组将提供自己的状态对象
■Scope.Benchmark:相同类型的所有实例将在所有工作线程之间共享
●OutputTimeUnit:默认时间单位

程序执行输出内容介绍
●Result内容介绍(因为测试的是 ops,单位是 秒,下面的结果都是基于 ops/s 来说):
○min:最小值
○avg:平均值
○max:最大值
○stdev:标准差,对于平均值的分散程度(一般来讲越小越接近平均值)
●最终结果介绍:
○Benchmark:jmh程序名
○Mode:程序中设定的 BenchmarkMode
○Cnt:总执行次数(不包含预热)
○Score:格式是 结果是xxx ± xxx,单位时间内的结果,对本程序来说就是 ops/s
○Error:
○Units:单位

代码部分
程序介绍
●程序一:通过synchronized关键字实现的生产者消费者程序
●程序二:通过ReentrantLock实现的生产者消费者程序,将生产者消费者的队列区分开,减少不必要的争抢
结果理论值
程序二相比程序一来说,少了线程的争抢,吞吐量要高一些。
具体程序

<properties><!-- 指定 jmh 版本号 --><version.jmh-core>1.25.2</version.jmh-core></properties><dependencies><!-- 引入 jmh --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>${version.jmh-core}</version></dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>${version.jmh-core}</version></dependency></dependencies>
/** 被测试程序 1*/
package com.zhqy.juc.producerAndConsumer.jmh;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.LinkedList;/*** <h3>通过 synchronized notify wait 关键字实现生产者、消费者工具</h3>** @author wangshuaijing* @version 1.0.0* @date 2020/11/4 5:08 下午*/
public class SynchronizedVersion {private static final Logger LOGGER = LoggerFactory.getLogger(SynchronizedVersion.class);private static final int MAX = 20;private final LinkedList<Object> linkedList = new LinkedList<>();public synchronized void push(Object x) {LOGGER.debug("生产者 - 进入对象锁 list数量:{}", linkedList.size());while (linkedList.size() >= MAX) {try {LOGGER.debug("生产者 - 开始休眠 list数量:{}", linkedList.size());wait();} catch (InterruptedException e) {e.printStackTrace();}}// 将数据放入linkedList.add(x);LOGGER.debug("生产者 - 放入数据 {} 后 list数量:{}", x, linkedList.size());notifyAll();}public synchronized Object pop() {LOGGER.debug("消费者 - 进入对象锁 list数量:{}", linkedList.size());while (linkedList.size() <= 0) {try {LOGGER.debug("消费者 - 开始休眠 list数量:{}", linkedList.size());wait();} catch (InterruptedException e) {e.printStackTrace();}}// 取出数据Object last = linkedList.removeLast();LOGGER.debug("消费者 - 消费 {},list数量:{}", last, linkedList.size());notifyAll();return last;}}
/*
* 测试程序 1
*/import org.openjdk.jmh.annotations.*;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 10, time = 5)
@Measurement(iterations = 100, time = 10)
@Threads(Threads.MAX)
@Fork(3)
@State(value = Scope.Thread)
@OutputTimeUnit(TimeUnit.SECONDS)
public class SynchronizedVersionTest {// 这一版已经解决问题private static final SynchronizedVersion TEST = new SynchronizedVersion();@Benchmarkpublic void test() throws InterruptedException {// 记录总元素数量CountDownLatch countDownLatch = new CountDownLatch(100);// 用2个线程生产100个元素for (int i = 0; i < 2; i++) {new Thread(() -> {for (int j = 0; j < 50; j++) {TEST.push(1);try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 用100个线程消费所有元素for (int i = 0; i < 100; i++) {new Thread(() -> {try {TEST.pop();} finally {// 每消费一次,不论成功失败,都进行计数countDownLatch.countDown();}}).start();}// 阻断等待,等到所有元素消费完成后,自动放开countDownLatch.await();}
}
# 程序1 测试结果Result "com.zhqy.juc.producerAndConsumer.jmh.SynchronizedVersionTest.test":36.339 ±(99.9%) 0.477 ops/s [Average](min, avg, max) = (31.214, 36.339, 44.255), stdev = 2.486CI (99.9%): [35.862, 36.816] (assumes normal distribution)# Run complete. Total time: 00:53:56REMEMBER: 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.Benchmark                                              Mode  Cnt   Score   Error  Units
producerAndConsumer.jmh.SynchronizedVersionTest.test  thrpt  300  36.339 ± 0.477  ops/s
/** 被测试程序 2*/
package com.zhqy.juc.producerAndConsumer.jmh;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;/*** <h3>通过 可重入锁 实现生产者、消费者,生产者、消费者独立使用通知队列</h3>** @author wangshuaijing* @version 1.0.0* @date 2020/11/4 5:08 下午*/
public class ReentrantLockVersion {private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantLockVersion.class);/*** 容器中的最大数量*/private static final int MAX = 20;private final LinkedList<Object> linkedList = new LinkedList<>();/*** 定义一个 可重入锁*/private final ReentrantLock reentrantLock = new ReentrantLock();/*** 为生产者定义一个独立的队列*/private final Condition producerLock = reentrantLock.newCondition();/*** 为消费者定义一个独立的队列*/private final Condition consumerLock = reentrantLock.newCondition();public void push(Object x) {try {reentrantLock.lock();LOGGER.debug("生产者 - 进入对象锁 list数量:{}", linkedList.size());while (linkedList.size() >= MAX) {LOGGER.debug("生产者 - 开始休眠 list数量:{}", linkedList.size());producerLock.await();}linkedList.add(x);LOGGER.debug("生产者 - 放入数据 {} 后 list数量:{}", x, linkedList.size());consumerLock.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {reentrantLock.unlock();}}public Object pop() {try {reentrantLock.lock();LOGGER.debug("消费者 - 进入对象锁 list数量:{}", linkedList.size());while (linkedList.size() <= 0) {LOGGER.debug("消费者 - 开始休眠 list数量:{}", linkedList.size());consumerLock.await();}Object last = linkedList.removeLast();LOGGER.debug("消费者 - 消费 {},list数量:{}", last, linkedList.size());producerLock.signalAll();return last;} catch (InterruptedException e) {e.printStackTrace();return null;} finally {reentrantLock.unlock();}}}
/*
* 测试程序 2
*/
package com.zhqy.juc.producerAndConsumer.jmh;import org.openjdk.jmh.annotations.*;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 10, time = 5)
@Measurement(iterations = 100, time = 10)
@Threads(Threads.MAX)
@Fork(3)
@State(value = Scope.Thread)
@OutputTimeUnit(TimeUnit.SECONDS)
public class ReentrantLockVersionTest {// 这一版已经解决问题private static final ReentrantLockVersion TEST = new ReentrantLockVersion();@Benchmarkpublic void test() throws InterruptedException {// 记录总元素数量CountDownLatch countDownLatch = new CountDownLatch(100);// 用2个线程生产100个元素for (int i = 0; i < 2; i++) {new Thread(() -> {for (int j = 0; j < 50; j++) {TEST.push(1);try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 用100个线程消费所有元素for (int i = 0; i < 100; i++) {new Thread(() -> {try {TEST.pop();} finally {// 每消费一次,不论成功失败,都进行计数countDownLatch.countDown();}}).start();}// 阻断等待,等到所有元素消费完成后,自动放开countDownLatch.await();}
}
# 程序2测试结果Result "com.zhqy.juc.producerAndConsumer.jmh.ReentrantLockVersionTest.test":39.203 ±(99.9%) 0.282 ops/s [Average](min, avg, max) = (35.262, 39.203, 44.288), stdev = 1.472CI (99.9%): [38.921, 39.486] (assumes normal distribution)# Run complete. Total time: 00:53:51REMEMBER: 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.Benchmark                                               Mode  Cnt   Score   Error  Units
producerAndConsumer.jmh.ReentrantLockVersionTest.test  thrpt  300  39.203 ± 0.282  ops/s

最终结果
●与理论值相同,程序二(通过ReentrantLock,分开生产者、消费者队列)降低了不必要的线程的争抢,增加了最终的吞吐量。
●jmh还可以用来排查并发问题 ^_^

特别说明
如果需要在springboot项目中运行,则需要通过程序启动springboot容器,然后从容器中获取自己需要的对象。具体程序如下:

/**
* setup初始化容器的时候只执行一次<br>
* Level.Trial 代表在 @Benchmark 注解的方法之前运行(具体运行的次数,由 @Threads 和 @State 共同决定。如果 @State 是 Scope.Thread,运行次数则为 @Threads 配置的线程数;如果 @State 是 Scope.Benchmark,运行次数则为1)<br>
* 运行次数值针对每一个 Fork 来说,新的Fork,会重新运行
*/
@Setup(Level.Trial)
public void init() {ConfigurableApplicationContext context = SpringApplication.run(BootApplication.class);xxxService = context.getBean(XxxService.class);
}

若有收获,就点个赞吧

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VBA即用型代码手册:删除Excel中空白行Delete Blank Rows in Excel

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。可以大大提高自己的劳动效率&#xff0c;而且可以提高数据的准确性。我这里专注VBA,将我多年的经验汇集在VBA系列九套教程中。 作为我的学员要利用我的积木编程思想&#xff0c;积木编程最重要的是积木如何搭建…

IDEA中好用的插件

IDEA中好用的插件 CodeGeeXMybatis Smart Code Help ProAlibaba Java Coding Guidelines​(XenoAmess TPM)​通义灵码常用操作 TranslationStatistic CodeGeeX 官网地址&#xff1a;https://codegeex.cn/ 使用手册&#xff1a;https://zhipu-ai.feishu.cn/wiki/CuvxwUDDqiErQU…

Android 自定义图片进度条

用系统的Progressbar&#xff0c;设置图片drawable作为进度条会出现图片长度不好控制&#xff0c;容易被截断&#xff0c;或者变形的问题。而我有个需求&#xff0c;使用图片背景&#xff0c;和图片进度&#xff0c;而且在进度条头部有个闪光点效果。 如下图&#xff1a; 找了…

速盾:流量攻击防护DDOS有哪几种有效的防御措施?

DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一种网络攻击方式&#xff0c;攻击者通过向目标服务器发送大量的请求&#xff0c;超出其处理能力&#xff0c;导致服务器无法正常运行&#xff0c;从而使服务中断或降级。为了保护网络安全&#xff0c;减少DDoS攻击对网站和服…

Kafka(十三)监控与告警

目录 Kafka监控与告警1 解决方案1.2 基础知识JMX监控指标代理查看KafkaJMX远程端口 1.3 真实案例Kafka Exporter:PromethusPromethus Alert ManagerGrafana 1.3 实际操作部署监控和告警系统1.2.1 部署Kafka Exporter1.2.2 部署Prometheus1.2.3 部署AlertManger1.2.4 添加告警规…

大疆上云API本地部署与飞机上云

文章目录 前言一、安装基础环境1. EMQX 安装(版本4.4.0)2. MySql 安装(版本8.0.26)3. Redis 安装 二、部署后端&#xff08;JDK必须11及以上&#xff09;三、部署前端四、成为大疆开发者五、飞机注册上云六、绑定飞机七、无人机状态查看八、直播流查看 前言 大疆上云API官方文…

HarmonyOS鸿蒙应用开发——ArkTS的“内置组件 + 样式 + 循环和条件渲染”

一、内置组件是咩&#xff1f; 学过前端的都知道&#xff0c;一个组件就是由多个组件组成的&#xff0c;一个组件也可以是多个小组件组成的&#xff0c;组件就是一些什么导航栏、底部、按钮......啥的&#xff0c;但是组件分为【自定义组件】跟【内置组件】 【自定义组件】就…

Web开发核心

文章目录 1.http协议简介2.http协议特性3.http请求和响应协议4.最简单的Web程序5.基于flask搭建web⽹站6.浏览器开发者⼯具&#xff08;重点&#xff09; 1.http协议简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写&#xff0c;是用于 万维网(WWW:Norld W…

【狂神说Java】Redis笔记以及拓展

一、Redis 入门 Redis为什么单线程还这么快&#xff1f; 误区1&#xff1a;高性能的服务器一定是多线程的&#xff1f; 误区2&#xff1a;多线程&#xff08;CPU上下文会切换&#xff01;&#xff09;一定比单线程效率高&#xff01; 核心&#xff1a;Redis是将所有的数据放在内…

用于时间序列概率预测的蒙特卡洛模拟

大家好&#xff0c;蒙特卡洛模拟是一种广泛应用于各个领域的计算技术&#xff0c;它通过从概率分布中随机抽取大量样本&#xff0c;并对结果进行统计分析&#xff0c;从而模拟复杂系统的行为。这种技术具有很强的适用性&#xff0c;在金融建模、工程设计、物理模拟、运筹优化以…

【C语言】C语言-设备管理系统(源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

AI大模型:大数据+大算力+强算法

前言&#xff1a;好久不见&#xff0c;甚是想念&#xff0c;我是辣条&#xff0c;我又回来啦&#xff0c;兄弟们&#xff0c;一别两年&#xff0c;还有多少老哥们在呢&#xff1f; 目录 一年半没更文我干啥去了&#xff1f; AI大模型火了 人工智能 大模型的理解 为什么学习…

ComfyUI完全入门:图生图局部重绘

大家好&#xff0c;我是每天分享AI应用的萤火君&#xff01; 这篇文章的主题和美女有关&#xff0c;不过并不是教大家生产美女视频&#xff0c;而是讲解 ComfyUI 的图生图局部重绘&#xff0c;其中将会以美女图片为例&#xff0c;来展示局部重绘的强大威力。 先看看效果&…

2024年5月26日 十二生肖 今日运势

小运播报&#xff1a;2024年5月26日&#xff0c;星期日&#xff0c;农历四月十九 &#xff08;甲辰年己巳月庚寅日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;马、猪、狗 需要注意&#xff1a;牛、蛇、猴 喜神方位&#xff1a;西北方 财神方位&#xff1a;…

java hashmap在项目中的使用

java hashmap在项目中的使用 1&#xff0c;缓存机制&#xff1a; 在需要频繁访问数据但又不想每次都从数据库或远程服务获取的场景中&#xff0c;可以使用 HashMap 作为缓存。例如&#xff0c;在一个 Web 应用程序中&#xff0c;用户信息可能只需要在登录时从数据库检索一次&a…

解释器和编译器(程序语言基础)

一、解释器 解释器则是一种逐行或逐段地解释执行源代码的工具。解释器会直接读取源代码&#xff0c;并在运行时逐行或逐段地解释执行代码&#xff0c;不生成独立的目标代码文件。解释器适用于一些动态语言&#xff0c;允许用户在代码执行过程中进行交互&#xff0c;更容易调试…

【linux_常用的指令】

笔记 1连接远程主机2 两台主机间复制2.1 查看当前目录2.2 普通复制 3 创建能运行sudo命令的用户3.1 更改用户admin的密码3.2 切换到admin用户&#xff0c;并且启动一个新的shell3.3 更改文件或目录的权限 4 切换目录5 解.tar.gz格式的压缩包6 运行.sh文件7 查看当前目录的所有文…

泛型中K T V E ? Object等分别代表的含义

E – Element (在集合中使用&#xff0c;因为集合中存放的是元素) T – Type&#xff08;Java 类&#xff09; K – Key&#xff08;键&#xff09; V – Value&#xff08;值&#xff09; N – Number&#xff08;数值类型&#xff09; &#xff1f; – 表示不确定的java类型&…

一个月速刷leetcodeHOT100 day07 轮转数组 除自身以外的乘积 找到字符串中所有字母异位词

轮转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: […

系统思考—跳出症状看全局

结束了《系统思考—跳出症状看全局》的迭代课程后&#xff0c;我感触颇深。通过一个深入的案例研讨、互动讨论和实战演练&#xff0c;学员们不仅更好地理解了如何跳出症状看全局&#xff0c;还掌握了制定更具前瞻性和可持续性策略的方法。我们还探讨了如何在实际工作中应用这些…