JMH的使用和代碼

概述

JMH 是一個由 OpenJDK/Oracle 裏面那羣開發了 Java 編譯器的大牛們所開發的 Micro Benchmark Framework 。何謂 Micro Benchmark 呢?簡單地說就是在 method 層面上的 benchmark,精度可以精確到微秒級。可以看出 JMH 主要使用在當你已經找出了熱點函數,而需要對熱點函數進行進一步的優化時,就可以使用 JMH 對優化的效果進行定量的分析。

使用場景

(1)想定量地知道某個函數需要執行多長時間,以及執行時間和輸入 n 的相關性
(2)一個函數有兩種不同實現(例如實現 A 使用了 FixedThreadPool,實現 B 使用了 ForkJoinPool),不知道哪種實現性能更好

配置

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

代碼

package com.songguo.jmhtest.JMH;

import org.openjdk.jmh.annotations.*;
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;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JMHFirstBenchmark {


    @Benchmark//對要被測試性能的代碼添加註解,說明該方法是要被測試性能的
    public int sleepAWhile() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            // ignore
        }
        return 0;
    }


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHFirstBenchmark.class.getSimpleName())
                .forks(1)
                .warmupIterations(3)
                .measurementIterations(3)
                .build();

        new Runner(opt).run();
    }
}

在這裏插入圖片描述
我再把代碼貼出來

/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53834:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/tools.jar:/Users/sgcx251/rqk/jmhtest/target/classes:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.1.7.RELEASE/spring-boot-starter-web-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-starter/2.1.7.RELEASE/spring-boot-starter-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot/2.1.7.RELEASE/spring-boot-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.7.RELEASE/spring-boot-autoconfigure-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.1.7.RELEASE/spring-boot-starter-logging-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/sgcx251/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/sgcx251/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.11.2/log4j-to-slf4j-2.11.2.jar:/Users/sgcx251/.m2/repository/org/apache/logging/log4j/log4j-api/2.11.2/log4j-api-2.11.2.jar:/Users/sgcx251/.m2/repository/org/slf4j/jul-to-slf4j/1.7.26/jul-to-slf4j-1.7.26.jar:/Users/sgcx251/.m2/repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar:/Users/sgcx251/.m2/repository/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.1.7.RELEASE/spring-boot-starter-json-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.9/jackson-databind-2.9.9.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.9/jackson-core-2.9.9.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.9/jackson-datatype-jdk8-2.9.9.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.9/jackson-datatype-jsr310-2.9.9.jar:/Users/sgcx251/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.9/jackson-module-parameter-names-2.9.9.jar:/Users/sgcx251/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.1.7.RELEASE/spring-boot-starter-tomcat-2.1.7.RELEASE.jar:/Users/sgcx251/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.22/tomcat-embed-core-9.0.22.jar:/Users/sgcx251/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.22/tomcat-embed-el-9.0.22.jar:/Users/sgcx251/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.22/tomcat-embed-websocket-9.0.22.jar:/Users/sgcx251/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.17.Final/hibernate-validator-6.0.17.Final.jar:/Users/sgcx251/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/sgcx251/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/sgcx251/.m2/repository/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-web/5.1.9.RELEASE/spring-web-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-beans/5.1.9.RELEASE/spring-beans-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-webmvc/5.1.9.RELEASE/spring-webmvc-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-aop/5.1.9.RELEASE/spring-aop-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-context/5.1.9.RELEASE/spring-context-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-expression/5.1.9.RELEASE/spring-expression-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/mysql/mysql-connector-java/8.0.17/mysql-connector-java-8.0.17.jar:/Users/sgcx251/.m2/repository/org/slf4j/slf4j-api/1.7.26/slf4j-api-1.7.26.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-core/5.1.9.RELEASE/spring-core-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/springframework/spring-jcl/5.1.9.RELEASE/spring-jcl-5.1.9.RELEASE.jar:/Users/sgcx251/.m2/repository/org/openjdk/jmh/jmh-core/1.19/jmh-core-1.19.jar:/Users/sgcx251/.m2/repository/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar:/Users/sgcx251/.m2/repository/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar com.songguo.jmhtest.JMH.JMHFirstBenchmark
# JMH version: 1.19
# VM version: JDK 1.8.0_201, VM 25.201-b09
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53834:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.songguo.jmhtest.JMH.JMHFirstBenchmark.sleepAWhile

# Run progress: 0.00% complete, ETA 00:00:06
# Fork: 1 of 1
# Warmup Iteration   1: 51319.974 us/op
# Warmup Iteration   2: 51825.236 us/op
# Warmup Iteration   3: 52087.356 us/op
Iteration   1: 51217.424 us/op
Iteration   2: 52467.070 us/op
Iteration   3: 52034.209 us/op


Result "com.songguo.jmhtest.JMH.JMHFirstBenchmark.sleepAWhile":
  51906.234 ±(99.9%) 11577.038 us/op [Average]
  (min, avg, max) = (51217.424, 51906.234, 52467.070), stdev = 634.577
  CI (99.9%): [40329.197, 63483.272] (assumes normal distribution)


# Run complete. Total time: 00:00:10

Benchmark                          Mode  Cnt      Score       Error  Units
JMH.JMHFirstBenchmark.sleepAWhile  avgt    3  51906.234 ± 11577.038  us/op

Process finished with exit code 0

我們 用的sleep的時間是·平均約爲50毫秒,所以我們最終的結果
51906.234 ±(99.9%) 11577.038 us/op [Average]

基本概念
Mode
Mode 表示 JMH 進行 Benchmark 時所使用的模式。通常是測量的維度不同,或是測量的方式不同。目前 JMH 共有四種模式:
(1).Throughput: 整體吞吐量,例如“1秒內可以執行多少次調用”。
(2).AverageTime: 調用的平均時間,例如“每次調用平均耗時xxx毫秒”。
(3).SampleTime: 隨機取樣,最後輸出取樣結果的分佈,例如“99%的調用在xxx毫秒以內,99.99%的調用在xxx毫秒以內”
(4).SingleShotTime: 以上模式都是默認一次 iteration 是 1s,唯有 SingleShotTime 是隻運行一次。往往同時把 warmup 次數設爲0,用於測試冷啓動時的性能。

Iteration
Iteration是JMH進行測試的最小單位。大部分模式下,iteration代表的是一秒,JMH會在這一秒內不斷調用需要benchmark的方法,然後根據模式對其採樣,計算吞吐量,計算平均執行時間等。
Warmup
Warmup是指在實際進行Benchmark前先進行預熱的行爲。因爲JVM的JIT機制的存在,如果某個函數被調用多次以後,JVM會嘗試將其編譯成爲機器碼從而提高執行速度。所以爲了讓benchmark的結果更加接近真實情況就需要進行預熱。

註解 整理

@Benchmark
表示該方法是需要進行 benchmark 的對象,用法和 JUnit 的 @Test 類似。
@Mode
Mode 如之前所說,表示 JMH 進行 Benchmark 時所使用的模式。
@State
State 用於聲明某個類是一個“狀態”,然後接受一個 Scope 參數用來表示該狀態的共享範圍。因爲很多 benchmark 會需要一些表示狀態的類,JMH 允許你把這些類以依賴注入的方式注入到 benchmark 函數裏。Scope 主要分爲兩種。
(1).Thread: 該狀態爲每個線程獨享。
(2).Benchmark: 該狀態在所有線程間共享。
關於State的用法,官方的 code sample 裏有比較好的例子。
@OutputTimeUnit
benchmark 結果所使用的時間單位。

啓動選項

JMH 在啓動前設置的參數。

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHFirstBenchmark.class.getSimpleName())
                .forks(1)
                .warmupIterations(3)
                .measurementIterations(3)
                .build();

        new Runner(opt).run();
    }

include
benchmark 所在的類的名字,注意這裏是使用正則表達式對所有類進行匹配的。
fork
進行 fork 的次數。如果 fork 數是2的話,則 JMH 會 fork 出兩個進程來進行測試。
warmupIterations
預熱的迭代次數。
measurementIterations
實際測量的迭代次數。

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