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.pswp.cn/bicheng/15560.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15560.shtml
英文地址,請注明出處:http://en.pswp.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;還掌握了制定更具前瞻性和可持續性策略的方法。我們還探討了如何在實際工作中應用這些…