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

  • 一、概覽
  • 二、jmh 簡介
  • 三、jmh 使用demo
  • 四、jmh 經常使用設置介紹
  • 五、注意事項
你的努力,終將成就無可替代的本身
未來的你必定會感謝如今拼命的本身

一、概覽

在平常開發中,咱們每每須要優化咱們本身寫的代碼。優化後的代碼,執行效率是否比以前的還高?具體高多少?這些都是須要去測量。
目前比較主流的作法是使用 jmh 進行微基準測試。java

二、jmh 簡介

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

在使用 jmh 以前,咱們每每會先經過各類工具(jvisualvm)找到熱點代碼, 而後再對熱點代碼使用 jmh 進行量化分析。app

三、jmh 使用demo

下面使用字符串拼接做爲案例介紹maven

第一步:加入依賴

maven 中引入 jmh jaride

<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() 追加字符。優化

四、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 註解。

五、注意事項

爲了不 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;
}

imageimage

相關文章
相關標籤/搜索