JMH各參數使用介紹

轉載自:趙伊凡's Blog

這裏說道的基準測試Benchmark其實是微基準測試Micro-Benchmark。這裏面的概念就不詳細介紹了,反正就是JMH可以非常方便的幫助我們進行java代碼的簡單基準測試。

有什麼用

可以對多組代碼進行基準測試比較。

很多人總說,這樣用速度快,性能好,別聽他們的,自己試過才知道。

Java的基準測試需要注意的幾個點

測試前需要預熱。
防止無用代碼進入測試方法中。
併發測試。
測試結果呈現。

簡單的代碼例子

我們來看一個簡單的示例,大家就知道JMH的強大了。

測試代碼如下。

package me.irfen.jmh;
 
import org.openjdk.jmh.annotations.*;
 
import java.util.concurrent.TimeUnit;
 
/**
 * author: zhaoye
 * date: 2016/11/4 14:36
 */
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(16)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StringBuilderTest {
 
    @Benchmark
    public void testStringAdd() {
        String a = "";
        for (int i = 0; i < 10; i++) {
            a += i;
        }
        print(a);
    }
 
    @Benchmark
    public void testStringBuilderAdd() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            sb.append(i);
        }
        print(sb.toString());
    }
 
    private void print(String a) {
    }
}

代碼中有好多註解,我們一會再說,先說我們要幹什麼。這裏應該很好看出來,大家都說String進行字符相加的時候沒有StringBuilder快,怎麼證明,則就是我們要做的事情。

現在來解釋註解

@Setup

這次我們沒用到,簡單說,這個註解的作用就是我們需要在測試之前進行一些準備工作,比如對一些數據的初始化之類的。。

@BenchmarkMode

基準測試類型。這裏選擇的是Throughput也就是吞吐量。根據源碼點進去,每種類型後面都有對應的解釋,比較好理解,吞吐量會得到單位時間內可以進行的操作數。

下面看下這裏的源碼。

Throughput("thrpt", "Throughput, ops/time"),
AverageTime("avgt", "Average time, time/op"),
SampleTime("sample", "Sampling time"),
SingleShotTime("ss", "Single shot invocation time"),
All("all", "All benchmark modes");

@Warmup

上面我們提到了,進行基準測試前需要進行預熱。一般我們前幾次進行程序測試的時候都會比較慢,所以要讓程序進行幾輪預熱,保證測試的準確性。其中的參數iterations也就非常好理解了,就是預熱輪數。

@Measurement

度量,其實就是一些基本的測試參數。iterations進行測試的輪次,time每輪進行的時長,timeUnit時長單位。都是一些基本的參數,可以根據具體情況調整。一般比較重的東西可以進行大量的測試,放到服務器上運行。

@Threads

測試線程,這個非常好理解,根據具體情況選擇,一般爲cpu乘以2。

@OutputTimeUnit

這個比較簡單了,基準測試結果的時間類型。一般選擇秒或者毫秒。

@State

當使用@Setup參數的時候,必須在類上加這個參數,不然會提示無法運行。

執行方式

生成jar執行

一般對於大型的測試,需要測試時間比較久,線程比較多的話,就需要去寫好了丟到linux程序裏執行,不然本機執行很久時間什麼都幹不了了。

$ mvn clean install
$ java -jar target/benchmarks.jar

首先編譯,然後執行就可以了。當然在執行的時候可以輸入-h參數來看幫助。

在IDE中執行

對於一些我們自己的一些小測試,就可以直接在IDE中執行了,還丟到linux上去太麻煩了。怎麼搞呢,看下面。

package me.irfen.jmh;
 
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
 
public class Test {
 
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder().include(StringBuilderTest.class.getSimpleName())
                .output("E:/Benchmark.log").forks(2).build();
        new Runner(options).run();
    }
}

執行的方法當然還是個main方法,當然你用junit也是可以的。這裏其實也比較簡單,new個Options,然後傳入要運行哪個測試,選擇基準測試報告輸出文件地址,設置fork出的線程數,然後通過Runner的run方法就可以跑起來了。

報告結果

下面我們來看下報告結果。

# JMH 1.14.1 (released 45 days ago)
# VM version: JDK 1.8.0_71, VM 25.71-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe
# VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 16 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: me.irfen.jmh.StringBuilderTest.testStringAdd
 
# Run progress: 0.00% complete, ETA 00:03:32
# Fork: 1 of 2
# Warmup Iteration   1: 14690.103 ops/ms
# Warmup Iteration   2: 18731.179 ops/ms
# Warmup Iteration   3: 19475.241 ops/ms
Iteration   1: 18787.224 ops/ms
Iteration   2: 18949.244 ops/ms
Iteration   3: 18715.979 ops/ms
Iteration   4: 18770.003 ops/ms
Iteration   5: 18912.926 ops/ms
Iteration   6: 19252.784 ops/ms
Iteration   7: 18975.545 ops/ms
Iteration   8: 18767.721 ops/ms
Iteration   9: 18976.551 ops/ms
Iteration  10: 18830.076 ops/ms
 
# Run progress: 25.00% complete, ETA 00:03:05
# Fork: 2 of 2
# Warmup Iteration   1: 15871.759 ops/ms
# Warmup Iteration   2: 19330.148 ops/ms
# Warmup Iteration   3: 18851.970 ops/ms
Iteration   1: 19058.934 ops/ms
Iteration   2: 18851.454 ops/ms
Iteration   3: 18511.584 ops/ms
Iteration   4: 18951.476 ops/ms
Iteration   5: 18721.372 ops/ms
Iteration   6: 18891.480 ops/ms
Iteration   7: 18651.455 ops/ms
Iteration   8: 18854.140 ops/ms
Iteration   9: 19198.246 ops/ms
Iteration  10: 19148.773 ops/ms
 
 
Result "testStringAdd":
  18888.848 ±(99.9%) 160.591 ops/ms [Average]
  (min, avg, max) = (18511.584, 18888.848, 19252.784), stdev = 184.936
  CI (99.9%): [18728.258, 19049.439] (assumes normal distribution)
 
 
# JMH 1.14.1 (released 45 days ago)
# VM version: JDK 1.8.0_71, VM 25.71-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe
# VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 16 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: me.irfen.jmh.StringBuilderTest.testStringBuilderAdd
 
# Run progress: 50.00% complete, ETA 00:02:04
# Fork: 1 of 2
# Warmup Iteration   1: 84345.885 ops/ms
# Warmup Iteration   2: 118411.934 ops/ms
# Warmup Iteration   3: 69339.009 ops/ms
Iteration   1: 74770.077 ops/ms
Iteration   2: 76996.804 ops/ms
Iteration   3: 77855.877 ops/ms
Iteration   4: 76883.502 ops/ms
Iteration   5: 75685.537 ops/ms
Iteration   6: 76299.372 ops/ms
Iteration   7: 77065.766 ops/ms
Iteration   8: 76537.937 ops/ms
Iteration   9: 77216.754 ops/ms
Iteration  10: 75410.698 ops/ms
 
# Run progress: 75.00% complete, ETA 00:01:02
# Fork: 2 of 2
# Warmup Iteration   1: 78998.331 ops/ms
# Warmup Iteration   2: 61750.531 ops/ms
# Warmup Iteration   3: 69513.539 ops/ms
Iteration   1: 64862.481 ops/ms
Iteration   2: 65963.130 ops/ms
Iteration   3: 66719.366 ops/ms
Iteration   4: 66315.918 ops/ms
Iteration   5: 67172.960 ops/ms
Iteration   6: 66156.558 ops/ms
Iteration   7: 67372.587 ops/ms
Iteration   8: 67607.029 ops/ms
Iteration   9: 67339.769 ops/ms
Iteration  10: 65845.035 ops/ms
 
 
Result "testStringBuilderAdd":
  71503.858 ±(99.9%) 4491.696 ops/ms [Average]
  (min, avg, max) = (64862.481, 71503.858, 77855.877), stdev = 5172.644
  CI (99.9%): [67012.162, 75995.554] (assumes normal distribution)
 
 
# Run complete. Total time: 00:04:11
 
Benchmark                                Mode  Cnt      Score      Error   Units
StringBuilderTest.testStringAdd         thrpt   20  18888.848 ±  160.591  ops/ms
StringBuilderTest.testStringBuilderAdd  thrpt   20  71503.858 ± 4491.696  ops/ms

內容有點多,仔細看,三大部分,第一部分是有序的結果,第二部分是無序的結果,第三部分就是兩個的簡單結果比較。這裏注意我們forks傳的2,所以每個測試有兩個fork結果。

前兩部分是一樣的,簡單說下。首先會寫出每部分的一些參數設置,然後是預熱迭代執行(Warmup Iteration),然後是正常的迭代執行(Iteration),最後是結果(Result)。這些看看就好,我們最關注的就是第三部分,其實也就是最終的結論。千萬別看歪了,他輸出的也確實很不爽,error那列其實沒有內容,score的結果是xxx ± xxx,單位是沒毫秒多少個操作。可以看到,StringBuilder的速度還確實是要比String進行文字疊加的效率好太多。

最後

到此我們介紹了JMH的一些簡單用法。對於別人說的東西,現在jvm優化了很多,哪些還是真的,哪些是已經過時的?只有自己試過了才知道。

官網在這,http://openjdk.java.net/projects/code-tools/jmh/
其他還有很多有趣的例子,http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章