關於四種獲取當前執行方法名稱方案的基準測試報告
來源:waynblog
本文是對作者上一篇文章中 Java 面試題之 Logback 列印日誌是如何獲取當前方法名稱的? 介紹的四種獲取當前執行方法名稱方案的基準測試報告。這四種方法如下,
使用 Thread.currentThread().getStackTrace()
方案使用異常物件的 getStackTrace()
方案使用匿名內部類的 getClass().getEnclosingMethod()
方案Java 9 的 Stack-Walking API 方案
本文將透過使用專業基準測試工具 JMH 來對如上四種方案進行測試。
基準測試,也稱之為效能測試,是一種用於衡量計算機系統,軟體應用或硬體元件效能的測試方法。基準測試旨在透過執行一系列標準化的任務場景來測量系統的效能表現,從而幫助評估系統的各種指標,如響應時間、吞吐量、延遲、資源利用率等。
JMH,即 Java Microbenchmark Harness,是專門用於程式碼微基準測試的工具套件。何謂 Micro Benchmark 呢?簡單的來說就是基於方法層面的基準測試,精度可以達到微秒級。其由 Oracle/openjdk 內部開發 JIT 編譯器的大佬們所開發,作為 Java 的方法級效能測試工具可以說是非常合適的。
測試環境是作者 2018 年購買的筆記本,配置如下,
重點看:
cpu:i7-8759H,6 核 12 執行緒 記憶體 16 GB(2667 MHZ)
前置準備
本文不打算做基準測試工具 JMH 的詳細科普文章,有興趣的大家自行百度 JMH 使用。所以我在這裡只給大家講解 JMH 的相關概念以及下文會用到的常用註解。
JMH 可以透過註解和配置引數來控制測試的環境和結果,例如預熱次數,迭代次數,執行緒數,時間單位等。它還可以生成詳細的測試報告,包括最小值,平均值,最大值,標準差,置信區間等。
JMH 相關概念
BeachMark:基準測試,主要用來測試一些方法的效能,可以根據不同的引數以不同的單位進行計算(可以使用平均時間作為單位,也可以使用吞吐量作為單位,可以在 BenchmarkMode 值進行調整)。 MIcro Benchmark:簡單地說就是在 method 層面上的 benchmark,精度可以精確到微秒級。 OPS:Operation Per Second:每秒操作量,是衡量效能的重要指標,數值的效能越好。類似的有:TPS、QPS。 Throughput:吞吐量。 Warmup:預熱,因為 JVM 的 JIT 機制的儲存,如果某個函式被呼叫多次之後,JVM 會嘗試將其編譯稱為機器碼從而提高執行速度。為了讓結果更加接近真實情況就需要進行預熱。
JMH 註解介紹
@Benchmark
:方法級註解,表示該方法是需要進行基準測試的物件,用法和 JUnit 的@Test
類似。@BenchmarkMode
:類級或方法級註解,用來指定基準測試的模式。有以下幾種模式可選:Throughput
:整體吞吐量,例如“1 秒內可以執行多少次呼叫”。AverageTime
:呼叫的平均時間,例如“每次呼叫平均耗時 xxx 毫秒”。SampleTime
:隨機取樣,最後輸出取樣結果的分佈,例如“99%的呼叫在 xxx 毫秒以內,99.99%的呼叫在 xxx 毫秒以內”。SingleShotTime
:單次呼叫時間,適合用於冷啟動測試,只執行一次,可以設定@Warmup(iterations = 0)
來禁用預熱。All
:上述所有模式的綜合。@OutputTimeUnit
:類級或方法級註解,用來指定基準測試結果的時間單位,可選的有NANOSECONDS
,MICROSECONDS
,MILLISECONDS
,SECONDS
等。@Warmup
:類級或方法級註解,用來配置預熱的引數,例如預熱的次數,每次預熱的時間,時間單位等。預熱的目的是為了讓 JVM 的 JIT 編譯器對程式碼進行最佳化,使基準測試的結果更加接近真實情況。@Measurement
:類級或方法級註解,用來配置實際執行基準測試的引數,例如測試的輪次,每輪的時間,時間單位等。@Threads
:類級或方法級註解,用來指定每個程式中的測試執行緒數,可以設定為Threads.MAX
來使用所有可用的執行緒。@Fork
:類級或方法級註解,用來指定進行 fork 的次數。如果 fork 數是 2 的話,則 JMH 會 fork 出兩個程式來進行測試。
1.使用 Thread.currentThread().getStackTrace()
方法
基準測試引數配置,
Warmup: 3 iterations, 10 s each
Measurement: 5 iterations, 3 s each
Timeout: 10 min per iteration
Threads: 8 threads, will synchronize iterations
Benchmark mode: Throughput
測試程式碼,
需要說明的是下面的四種測試方法的 JMH 註解配置以及 main 方法都是相同的。所以為了節約篇幅,突出重點,後面三種方案將省去 JMH 註解以及 main 方法配置。
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MethodNameTest {
@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m1() {
// 獲取當前方法名
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(MethodNameTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試結果,
...
# Run progress: 0.00% complete, ETA 00:06:00
# Fork: 1 of 2
# Warmup Iteration 1: 16.946 ops/ms
# Warmup Iteration 2: 17.086 ops/ms
# Warmup Iteration 3: 17.116 ops/ms
Iteration 1: 17.159 ops/ms
Iteration 2: 17.118 ops/ms
Iteration 3: 17.279 ops/ms
Iteration 4: 17.329 ops/ms
Iteration 5: 17.241 ops/ms
# Run progress: 12.50% complete, ETA 00:05:23
# Fork: 2 of 2
# Warmup Iteration 1: 16.546 ops/ms
# Warmup Iteration 2: 17.340 ops/ms
# Warmup Iteration 3: 17.431 ops/ms
Iteration 1: 17.331 ops/ms
Iteration 2: 17.099 ops/ms
Iteration 3: 17.280 ops/ms
Iteration 4: 17.511 ops/ms
Iteration 5: 17.323 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m1":
17.267 ±(99.9%) 0.184 ops/ms [Average]
(min, avg, max) = (17.099, 17.267, 17.511), stdev = 0.122
CI (99.9%): [17.083, 17.451] (assumes normal distribution)
上面一大堆輸出資訊,大家直接看重點,在最後 Result "ltd.newbee.mall.MethodNameTest.m1":
這裡,平均 ops 是每毫秒 17 次,比較低。
2.使用異常物件的 getStackTrace()
方法
測試程式碼,
@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m2() {
// 獲取當前方法名
String methodName = new Throwable().getStackTrace()[0].getMethodName();
}
測試結果,
...
# Run progress: 25.00% complete, ETA 00:04:37
# Fork: 1 of 2
# Warmup Iteration 1: 12.891 ops/ms
# Warmup Iteration 2: 12.873 ops/ms
# Warmup Iteration 3: 13.023 ops/ms
Iteration 1: 25.617 ops/ms
Iteration 2: 25.840 ops/ms
Iteration 3: 25.301 ops/ms
Iteration 4: 24.839 ops/ms
Iteration 5: 25.930 ops/ms
# Run progress: 37.49% complete, ETA 00:03:51
# Fork: 2 of 2
# Warmup Iteration 1: 12.511 ops/ms
# Warmup Iteration 2: 12.329 ops/ms
# Warmup Iteration 3: 13.011 ops/ms
Iteration 1: 23.842 ops/ms
Iteration 2: 24.292 ops/ms
Iteration 3: 25.600 ops/ms
Iteration 4: 25.745 ops/ms
Iteration 5: 25.789 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m2":
25.280 ±(99.9%) 1.088 ops/ms [Average]
(min, avg, max) = (23.842, 25.280, 25.930), stdev = 0.720
CI (99.9%): [24.191, 26.368] (assumes normal distribution)
直接看最後 Result "ltd.newbee.mall.MethodNameTest.m2":
這裡,平均 ops 是每毫秒 25 次,也比較低。
3.使用匿名內部類的 getClass().getEnclosingMethod()
方法
測試程式碼,
@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m1() {
// 獲取當前方法名
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
}
測試結果,
...
# Run progress: 49.99% complete, ETA 00:03:04
# Fork: 1 of 2
# Warmup Iteration 1: 10489.110 ops/ms
# Warmup Iteration 2: 9233.590 ops/ms
# Warmup Iteration 3: 10504.615 ops/ms
Iteration 1: 10695.898 ops/ms
Iteration 2: 10570.155 ops/ms
Iteration 3: 11089.810 ops/ms
Iteration 4: 10805.448 ops/ms
Iteration 5: 10027.222 ops/ms
# Run progress: 62.49% complete, ETA 00:02:18
# Fork: 2 of 2
# Warmup Iteration 1: 11322.008 ops/ms
# Warmup Iteration 2: 10025.593 ops/ms
# Warmup Iteration 3: 10808.095 ops/ms
Iteration 1: 10684.594 ops/ms
Iteration 2: 11241.540 ops/ms
Iteration 3: 10742.348 ops/ms
Iteration 4: 9940.437 ops/ms
Iteration 5: 11226.023 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m3":
10702.347 ±(99.9%) 672.631 ops/ms [Average]
(min, avg, max) = (9940.437, 10702.347, 11241.540), stdev = 444.904
CI (99.9%): [10029.716, 11374.979] (assumes normal distribution)
直接看最後 Result "ltd.newbee.mall.MethodNameTest.m3":
這裡,平均 ops 是每毫秒 10702 次。非常誇張,可以看到 ops 對比上面兩種方法一下子從幾十級別提升到上萬級別。
4.Java 9 的 Stack-Walking API
測試程式碼,
@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m1() {
// 獲取當前方法名
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
}
測試結果,
...
# Run progress: 74.99% complete, ETA 00:01:32
# Fork: 1 of 2
# Warmup Iteration 1: 2191.034 ops/ms
# Warmup Iteration 2: 2141.886 ops/ms
# Warmup Iteration 3: 2192.843 ops/ms
Iteration 1: 2262.279 ops/ms
Iteration 2: 2263.193 ops/ms
Iteration 3: 2201.354 ops/ms
Iteration 4: 2282.906 ops/ms
Iteration 5: 2130.322 ops/ms
# Run progress: 87.48% complete, ETA 00:00:46
# Fork: 2 of 2
# Warmup Iteration 1: 2207.800 ops/ms
# Warmup Iteration 2: 2269.887 ops/ms
# Warmup Iteration 3: 2239.005 ops/ms
Iteration 1: 2001.840 ops/ms
Iteration 2: 2047.698 ops/ms
Iteration 3: 2349.138 ops/ms
Iteration 4: 2362.165 ops/ms
Iteration 5: 2305.982 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m4":
2220.688 ±(99.9%) 186.910 ops/ms [Average]
(min, avg, max) = (2001.840, 2220.688, 2362.165), stdev = 123.629
CI (99.9%): [2033.778, 2407.598] (assumes normal distribution)
直接看最後 Result "ltd.newbee.mall.MethodNameTest.m4":
這裡,平均 ops 是每毫秒 2220 次。對比 第一種和第二種方案的 幾十 ops 來說效能提升也很客觀,但是對比第三種方法的上萬級別 ops 還是不足。
四種方案的最終得分對比
Benchmark Mode Cnt Score Error Units
MethodNameTest.m1 thrpt 10 17.267 ± 0.184 ops/ms
MethodNameTest.m2 thrpt 10 25.280 ± 1.088 ops/ms
MethodNameTest.m3 thrpt 10 10702.347 ± 672.631 ops/ms
MethodNameTest.m4 thrpt 10 2220.688 ± 186.910 ops/ms
MethodNameTest.m1 ss 10 0.686 ± 0.289 ms/op
MethodNameTest.m2 ss 10 0.339 ± 0.287 ms/op
MethodNameTest.m3 ss 10 0.031 ± 0.011 ms/op
MethodNameTest.m4 ss 10 0.074 ± 0.027 ms/op
根據最後得分可以看出,四種方案中效能最好的方案是基於匿名內部類的 getClass().getEnclosingMethod()
方案,效能第二是的是基於 Java 9 出現的 Stack-Walking API 方案,其他兩種效能過於低下了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2995112/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java:如何輕鬆獲取當前執行的方法名Java
- [系列] Go - 基於 GORM 獲取當前請求所執行的 SQL 資訊GoORMSQL
- Java如何獲取當前執行緒Java執行緒
- 簡單實現Laravel獲取當前執行的SQLLaravelSQL
- 第三方驗收測試報告有什麼作用?如何獲取權威軟體測試報告?測試報告
- 軟體確認測試報告有什麼作用?如何獲取第三方軟體測試報告?測試報告
- httprunner(11)執行測試報告HTTP測試報告
- 一鍵獲取測試指令碼,輕鬆驗證“TSBS 時序資料庫效能基準測試報告”指令碼資料庫測試報告
- 軟體安全測試要交給第三方軟體測試嗎?安全測試報告報價獲取測試報告
- Python如何獲取當前執行檔案路徑?Python
- Java 面試題之 Logback 列印日誌是如何獲取當前方法名稱的?Java面試題
- android應用開發—獲取當前執行的services列表Android
- TP6 多應用模式下獲取不到當前控制器和方法名模式
- 獲取當前修改的行記錄資料
- 軟體測試報告包含哪些內容?如何獲取高質量軟體測試報告?測試報告
- Qt獲取當前時間的兩種方式筆記QT筆記
- JavaScript 獲取當前月份JavaScript
- JavaScript 獲取當前域名JavaScript
- javascript獲取當前urlJavaScript
- 設定滑鼠捕獲屬於當前執行緒的指定視窗執行緒
- php 獲取當前域名和當前協議PHP協議
- PHP中獲取當前頁面的各種URL格式PHP
- 【譯】關於四種快取的故事快取
- Spring6 當中 獲取 Bean 的四種方式SpringBean
- iOS獲取當前裝置的資訊-網路相關iOS
- mybatis獲取當前時間MyBatis
- Java獲取當前星期幾Java
- JQuery獲取當前元素本身jQuery
- java獲取當前時間Java
- Qt獲取當前時間QT
- js獲取當前時間JS
- Qt 獲取當前時間QT
- jQuery如何獲取當前元素的索引jQuery索引
- javascript獲取當前的時間戳JavaScript時間戳
- 獲取當前會話資訊的方法會話
- 獲取執行計劃的6種方法
- FormRequest 自定義獲取方法名字ORM
- viewpager獲取當前view報空指標的解決方法Viewpager指標