1. 程式人生 > 其它 >JMH -- JAVA 微基準測試工具套件

JMH -- JAVA 微基準測試工具套件

技術標籤:javajava

文章目錄

你的努力,終將成就無可替代的自己
將來的你一定會感謝現在拼命的自己

1、概覽

在日常開發中,我們往往需要優化我們自己寫的程式碼。優化後的程式碼,執行效率是否比之前的還高?具體高多少?這些都是需要去測量。
目前比較主流的做法是使用 jmh 進行微基準測試。

2、jmh 簡介

jmhjava 用於微基準測試工具套件。主要是基於方法層面的基準測試,精度可達納秒級。由 oracle 實現 JIT 大牛編寫而成。

在使用 jmh 之前,我們往往會先通過各種工具(jvisualvm

)找到熱點程式碼, 然後再對熱點程式碼使用 jmh 進行量化分析。

3、jmh 使用demo

下面使用字串拼接作為案例介紹

第一步:加入依賴

maven 中引入 jmh jar

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.0</version>
</dependency>

<dependency>
    <groupId
>
org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.0</version> <scope>provided</scope> </dependency>

第二步:編寫基準測試

接下來,建立測試類,來判斷 + 還是 StringBuilder.append() 吞吐量更高

@BenchmarkMode(Mode.Throughput)
@Warmup(iterations =
3) @Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) @Threads(1) @Fork(1) @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class StringBenchmark { String a = "1"; String b = "2"; String c = "3"; @Benchmark public String builderBenchmark() { return new StringBuilder().append(a).append(b).append(c).toString(); } @Benchmark public String connectionBenchmark() { return a + b + c; } public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(StringBenchmark.class.getSimpleName()) .build(); new Runner(options).run(); } }

第三步:檢視執行結果

# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 5 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.csp.boot.jmh.StringBenchmark.builderBenchmark

以上輸出來自於我們的配置。
第一行表示預熱 3 次,每次 1 秒。
第二行表示執行 3 次,每次執行 5 秒。
第三行表示 1 個執行緒執行
第四行表示統計的資料緯度為吞吐量


# Run progress: 0.00% complete, ETA 00:00:36
# Fork: 1 of 1
# Warmup Iteration   1: 27694.373 ops/ms
# Warmup Iteration   2: 47351.819 ops/ms
# Warmup Iteration   3: 60008.968 ops/ms
Iteration   1: 65411.091 ops/ms
Iteration   2: 64443.826 ops/ms
Iteration   3: 65067.621 ops/ms

# Fork 表示子程序。因為只配置了 1 個,所以只有一個程序執行結果。
# Warmup Iteration 為預熱的資料,不會被計入統計,我們配置了 3 次預熱,所以有 3 個結果。
Iteration 方法執行的結果


Result: 64974.180 ±(99.9%) 8945.921 ops/ms [Average]
  Statistics: (min, avg, max) = (64443.826, 64974.180, 65411.091), stdev = 490.356
  Confidence interval (99.9%): [56028.259, 73920.100]

統計結果給出了多次測量後的最小值、均值,最大值,以及標準差(stdev),置信區間。


Benchmark             Mode   Samples  Score      Score error   Units
builderBenchmark      thrpt    3     64974.180     8945.921    ops/ms
connectionBenchmark   thrpt    3     63524.697    69103.252    ops/ms

在最後,會給出 2 個基準測試的效能對比。
從上面結果來看,使用 +StringBuilder.append() 吞吐量差不多,原因在於,+ 在編譯時,會使用 StringBuilder.append() 追加字元。

4、jmh 常用設定介紹

@BenchmarkMode
BenchmarkMode 為使用模式,可選值如下:
Mode.Throughput:吞吐量模式
AverageTime: 表示每次執行時間
SampleTime: 表示取樣時間
SingleShotTime: 表示只執行一次,用於測試冷啟動消費時間
All: 表示統計前面所有指標

@Warmup
配置預熱次數,本例是 3

@Measurement
配置執行次數,本例是執行 5 秒,總共執行 3 次。如果是做效能測試,預設使用 1 秒即可

@Threads
配置同時執行多少個執行緒,預設值是 Runtime.getRuntime().availableProcessors(),本例採用 1

@Fork
啟動多少個子程序分別測試每個被 @Benchmark 標識的方法,本例採用 1

@OutputTimeUnit
統計結果的時間單元,本例是 TimeUnit.MILLISECONDS

@Benchmark
用於標識哪些方法需要被測試

@State
一般而言,效能測試,都會引用一些外部的物件,jmh 要求必須設定外部變數的作用域。可以使用 @State 表示外部物件的作用域。@State 作用於類上,被 @State 標識的物件是在 Thread 範圍內還是在 Benchmark。如果是 Thread,則會為每個執行緒,單獨建立物件。如果是 Benchmark 則所有測試共享。
本例的外部變數為 a b c@State 值為 Benchmark

@Setup、@TearDown
2 個註解,均作用於方法上。@Setup 用於測試前的初始化工作; @TearDown 用於回收某些資源

@Param
指定某項引數的多種情況,特別適合用來測試一個函式在不同的引數輸入的情況下的效能,只能作用在欄位上,使用該註解必須定義 @State 註解。

5、注意事項

為了避免 JIT 優化。因此對於被測試方法,儘量把結果返回。例如以下這段程式碼,會因為 i 沒有被使用,而直接不執行 for 迴圈

public void add() {
    int i = 12;
    for (int j = 0; j < 12; j++) {
        i += j;
    }
}

正常的程式碼如下

public int add() {
    int i = 12;
    for (int j = 0; j < 12; j++) {
        i += j;
    }
    return i;
}

在這裡插入圖片描述
在這裡插入圖片描述