Java實現代碼塊耗時測算工具類
1. 測算耗時
背景
程序員經常需要知道一段代碼的執(zhí)行耗時。典型的例如查詢數據庫,不同參數查詢到不同的數據量,耗時相差很大。如果一個操作總體耗時較大,包含了幾次數據庫操作,自然就想知道哪一次操作是長耗時的主要原因,甚至每一個的耗時是多少。從而可以有的放矢地做優(yōu)化。
使用要簡單
測算一段代碼的耗時,通常就是用stop watch(秒表)。在執(zhí)行的開始點開始計時,在執(zhí)行的結束點停止計時,并把耗時輸出到日志。雖然代碼也就幾行,但每個地方都這么寫,會顯得很啰嗦,編碼效率也低。
所以我把這個功能封裝成一個實用工具類。用起來大概是這樣子:
MonitorUtil.time(() -> { // 一個耗時操作,如查詢數據庫 }, "給這個操作起個名");
它會這樣輸出到日志:
[WARN] 2023-05-19T08:26:00.007 xx-project com.xxx.util.MonitorUtil 給這個操作起個名 elapsed 3,140 ms. warning
末尾的warning會在某些終端上顯示為黃色,顯眼。
輸出要簡潔
在線上運行時,也希望能知道耗時比較大的代碼。但如果每處測算耗時的地方都輸出日志,日志就會太多,而我們只關注那些長耗時。所以我規(guī)定:少于500毫秒的,不輸出。
暫停與繼續(xù)
有一種場景,在一個代碼范圍內,只期望計算一部分操作的累計耗時,而忽略其它操作的耗時。
比如,處理一批文件,每個文件要解析后將結果存為另一個文件。這里只關心解析的時間,而忽略寫文件的耗時。所以MonitorUtil提供了暫停和繼續(xù)的功能。在寫文件時,暫停計時,寫文件后開始解析另一個文件時繼續(xù)計時。
最終會輸出累計的耗時以及累計等待耗時。累計等待耗時,即是從暫停到繼續(xù)的時長的總和。
代碼:
MonitorUtil.time(monitor -> { for (...) { // 文件解析 monitor.pause(); // 其它操作 monitor.continue(); } }, "僅算文件解析");
輸出如下:
[WARN] 2023-05-19T08:26:00.008 xx-project com.xxx.util.MonitorUtil 僅算文件解析 elapsed 1,140 ms,waited 2,000ms. warning
這樣就能知道那段代碼是工作的時間多還是等待的時間多了。
2. 顯示進度
說另一個問題,也是跟時間有關的。有些功能耗時比較長,比如循環(huán)處理數據?;蛟S是10秒,或許是10分鐘。對著沒有什么輸出的屏幕,顯得很無助。此時如果能知道剩下大概需要多少時間,無疑對做決策有大幫助。如,是中斷還是繼續(xù)等。
當然了,進度百分比,只有業(yè)務代碼才知道。但業(yè)務代碼難以把握如何輸出這個進度。密了則輸出一大堆,干擾視線。疏了又是漫長無助的等待。所以,是由業(yè)務代碼報告進度,由這個工具決定要不要輸出。
代碼:
MonitorUtil.time(monitor -> { for (...) { // 一些操作 monitor.process(proc); // proc 是[0,1]區(qū)間的浮點數,表示進度 } }, "給這個操作起個名");
要做到輸出不能太密,不能太疏。所以MonitorUtil內部的規(guī)則如下:
- 5秒內不輸出兩次
- 5~10秒,前進至少3%才輸出
- 10~15秒,前進至少1%才輸出
- 大于15秒,無論進度如何都輸出
這里說的多少秒,是相對于上一次輸出的時間而言。前進也是相對于上一次輸出。
輸出:
[INFO] 2023-05-19T08:26:00.009 xx-project com.xxx.util.MonitorUtil 給這個操作起個名 7% 30% 61% 98% 100%`
嵌套使用
假如有兩個進度測試的代碼嵌套在一起,如
MonitorUtil.time(monitor -> { for (...) { // 一些操作 abc(); // 一些操作 monitor.process(proc); // proc 是[0,1]區(qū)間的浮點數,表示進度 } }, "大功能名"); void abc() { MonitorUtil.time(monitor -> { for (...) { // 一些操作 monitor.process(proc); // proc 是[0,1]區(qū)間的浮點數,表示進度 } }, "小功能名"); }
由于日志只有一份,兩個進度的輸出就會混在一起,很亂,看不清。所以解決這個問題,我規(guī)定當發(fā)生進度測算嵌套時,只有最外層的有效,即只有最外層的會輸出。好像也沒有更好的辦法了,如果您有更好的建議,請留言。感謝。
3. 這個工具的優(yōu)勢
高性能
- 它的輸出都是被動的。即只在被測代碼開始、結束、主動調用process方法時,才執(zhí)行邏輯,才有可能輸出。如果process方法沒有被調用,就算過了15秒,也不會有進度輸出。所以,在應用的時候,可以偏頻繁一些地調用process方法。調了不一定有輸出,不調一定不會輸出。
- 它的內部是順序的判斷,沒有循環(huán),更沒有復雜算法,非常高效。
- 它并沒有創(chuàng)建新的線程(單線程),而是與業(yè)務代碼工作在同一個線程。它消耗的資源非常少,包括內存消耗很少。在除了“開始、結束、process方法被調用時”之外,消耗的CPU為零。用它來測算時間,成本極低,可以大量使用。
支持異常
業(yè)務代碼發(fā)生異常時,它還能不能測算出耗時?可以的?;卣{方法(lambda表達式)如果發(fā)生的異常,它也能計算耗時,并在耗時超過500毫秒時輸出。異常也是結束的一種。
測算的目標形式
如前面所見,它測算的目標形式是代碼塊,而代碼塊是最靈活的表現形式。它可以是一行代碼,可以是多行??梢允且粋€函數的完整代碼,也可以是函數的一部分代碼。
4. 實現原理簡介
它的內部實現原理并不難。測算時間,就是記錄(用變量保存)開始時間,并在結束時計算一下時間差。進度功能,則是記錄上一次的輸出時間和進度,下次輸出時作對比。暫停功能,則是用兩個變量分別記錄工作和等待的耗時累加值。至于如何知道自己是不是“最外層”,則是用一個static變量來保存當前的層數。如果是0就是最外層。
它的每一處實現,都是平淡無奇。借用棋類的話,就是“通盤無妙手”。關鍵是實用。
到此這篇關于Java實現代碼塊耗時測算工具類的文章就介紹到這了,更多相關Java代碼耗時內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot cloud使用eureka整合分布式事務組件Seata 的方法
這篇文章主要介紹了springboot cloud使用eureka整合分布式事務組件Seata 的方法 ,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05Java設計模式之工廠模式分析【簡單工廠、工廠方法、抽象工廠】
這篇文章主要介紹了Java設計模式之工廠模式,結合實例形式分析了簡單工廠、工廠方法、抽象工廠等相關功能、實現與使用方法,需要的朋友可以參考下2018-04-04SpringBoot開發(fā)實戰(zhàn)系列之動態(tài)定時任務
在我們日常的開發(fā)中,很多時候,定時任務都不是寫死的,而是寫到數據庫中,從而實現定時任務的動態(tài)配置,下面這篇文章主要給大家介紹了關于SpringBoot開發(fā)實戰(zhàn)系列之動態(tài)定時任務的相關資料,需要的朋友可以參考下2021-08-08java多線程編程之使用thread類創(chuàng)建線程
在Java中創(chuàng)建線程有兩種方法:使用Thread類和使用Runnable接口。在使用Runnable接口時需要建立一個Thread實例2014-01-01