JMH使用說明

来源:https://www.cnblogs.com/evenshu/archive/2018/11/07/9926586.html
-Advertisement-
Play Games

一、 性能往往是特定情景下的評價,泛泛地說性能“好”或者“快”,往是具有誤導性的。通過引入基準測試,我們可以定義性能對比的明確條件、具體的指標,進而保證得到定量的、可重覆的對比數據,這是工程中的實際需要。 不同的基準測試其具體內容和範圍也存在很大的不同。如果是專業的性能工程師,更加熟悉的可能是類似S ...


一、

  性能往往是特定情景下的評價,泛泛地說性能“好”或者“快”,往是具有誤導性的。通過引入基準測試,我們可以定義性能對比的明確條件、具體的指標,進而保證得到定量的、可重覆的對比數據,這是工程中的實際需要。

  不同的基準測試其具體內容和範圍也存在很大的不同。如果是專業的性能工程師,更加熟悉的可能是類似SPEC提供的工業標準的系統級測試;而對於大多數 Java 開發者,更熟悉的則是範圍相對較小、關註點更加細節的微基準測試(Micro-Benchmark)。

 

  目前應用最為廣泛的框架之一就是JMH,OpenJDK 自身也大量地使用 JMH 進行性能對比,如果你是做 Java API 級別的性能對比,JMH 往往是你的首選。

 

二、如果要在現有Maven項目中使用JMH,只需要把生成出來的兩個依賴以及shade插件拷貝到項目的pom中即可:

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
            <version>1.19</version>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.19</version>
            <scope>provided</scope>
        </dependency>


    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>microbenchmarks</finalName>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>org.openjdk.jmh.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

 

TestJmh.java

package com.jmh;

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
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;

@BenchmarkMode(Mode.Throughput) // 測試方法平均執行時間
@OutputTimeUnit(TimeUnit.MICROSECONDS) // 輸出結果的時間粒度為微秒
@State(Scope.Thread) // 每個測試線程一個實例
public class TestJmh {

    @Benchmark
        public String stringConcat() {
            String a = "a";
            String b = "b";
            String c = "c";
            String s = a + b + c;
            System.out.println(s);
            return s;
        }
        
        
        public static void main(String[] args) throws RunnerException {
            Options opt = new OptionsBuilder().include(TestJmh.class.getSimpleName()).forks(1).warmupIterations(5)
                    .measurementIterations(5).build();
            new Runner(opt).run();
        }
}

 

 

三、詳細說明

3.1 基本概念

首先看看JMH的幾個基本概念:

 

Mode 

Mode 表示 JMH 進行 Benchmark 時所使用的模式。通常是測量的維度不同,或是測量的方式不同。目前 JMH 共有四種模式:

 

Throughput: 整體吞吐量,例如“1秒內可以執行多少次調用”。

 

AverageTime: 調用的平均時間,例如“每次調用平均耗時xxx毫秒”。

 

SampleTime: 隨機取樣,最後輸出取樣結果的分佈,例如“99%的調用在xxx毫秒以內,99.99%的調用在xxx毫秒以內”

 

SingleShotTime: 以上模式都是預設一次 iteration 是 1s,唯有 SingleShotTime 是只運行一次。往往同時把 warmup 次數設為0,用於測試冷啟動時的性能。

 

Iteration 

Iteration 是 JMH 進行測試的最小單位。在大部分模式下,一次 iteration 代表的是一秒,JMH 會在這一秒內不斷調用需要 benchmark 的方法,然後根據模式對其採樣,計算吞吐量,計算平均執行時間等。

 

Warmup

 

Warmup 是指在實際進行 benchmark 前先進行預熱的行為。為什麼需要預熱?因為 JVM 的 JIT 機制的存在,如果某個函數被調用多次之後,JVM 會嘗試將其編譯成為機器碼從而提高執行速度。為了讓 benchmark 的結果更加接近真實情況就需要進行預熱。

 

3.2 註解與選項

3.2.1 常用註解說明

@BenchmarkMode 

對應Mode選項,可用於類或者方法上, 需要註意的是,這個註解的value是一個數組,可以把幾種Mode集合在一起執行,還可以設置為Mode.All,即全部執行一遍。

 

@State 

類註解,JMH測試類必須使用@State註解,State定義了一個類實例的生命周期,可以類比Spring Bean的Scope。由於JMH允許多線程同時執行測試,不同的選項含義如下:

 

Scope.Thread:預設的State,每個測試線程分配一個實例;

 

Scope.Benchmark:所有測試線程共用一個實例,用於測試有狀態實例在多線程共用下的性能;

 

Scope.Group:每個線程組共用一個實例;

 

@OutputTimeUnit 

benchmark 結果所使用的時間單位,可用於類或者方法註解,使用java.util.concurrent.TimeUnit中的標準時間單位。

 

@Benchmark 

方法註解,表示該方法是需要進行 benchmark 的對象。

 

@Setup 

方法註解,會在執行 benchmark 之前被執行,正如其名,主要用於初始化。

 

@TearDown 

方法註解,與@Setup 相對的,會在所有 benchmark 執行結束以後執行,主要用於資源的回收等。

 

@Param 

成員註解,可以用來指定某項參數的多種情況。特別適合用來測試一個函數在不同的參數輸入的情況下的性能。@Param註解接收一個String數組,在@setup方法執行前轉化為為對應的數據類型。多個@Param註解的成員之間是乘積關係,譬如有兩個用@Param註解的欄位,第一個有5個值,第二個欄位有2個值,那麼每個測試方法會跑5*2=10次。

 

原文:https://blog.csdn.net/lxbjkben/article/details/79410740 

JMH使用說明一、概述JMH,即Java Microbenchmark Harness,是專門用於代碼微基準測試的工具套件。何謂Micro Benchmark呢?簡單的來說就是基於方法層面的基準測試,精度可以達到微秒級。當你定位到熱點方法,希望進一步優化方法性能的時候,就可以使用JMH對優化的結果進行量化的分析。和其他競品相比——如果有的話,JMH最有特色的地方就是,它是由Oracle內部實現JIT的那撥人開發的,對於JIT以及JVM所謂的“profile guided optimization”對基準測試準確性的影響可謂心知肚明(smile)
JMH比較典型的應用場景有:
想準確的知道某個方法需要執行多長時間,以及執行時間和輸入之間的相關性;對比介面不同實現在給定條件下的吞吐量;查看多少百分比的請求在多長時間內完成;二、第一個例子接下來,我們看看如何使用JMH。
要使用JMH,首先需要準備好Maven環境,JMH的源代碼以及官方提供的Sample就是使用Maven進行項目管理的,github上也有使用gradle的例子可自行搜索參考。使用mvn命令行創建一個JMH工程:
mvn archetype:generate \          -DinteractiveMode=false \          -DarchetypeGroupId=org.openjdk.jmh \          -DarchetypeArtifactId=jmh-java-benchmark-archetype \          -DgroupId=co.speedar.infra \          -DartifactId=jmh-test \          -Dversion=1.01234567如果要在現有Maven項目中使用JMH,只需要把生成出來的兩個依賴以及shade插件拷貝到項目的pom中即可:
    <dependency>        <groupId>org.openjdk.jmh</groupId>        <artifactId>jmh-core</artifactId>        <version>0.7.1</version>    </dependency>    <dependency>        <groupId>org.openjdk.jmh</groupId>        <artifactId>jmh-generator-annprocess</artifactId>        <version>0.7.1</version>        <scope>provided</scope>    </dependency>...    <plugin>        <groupId>org.apache.maven.plugins</groupId>        <artifactId>maven-shade-plugin</artifactId>        <version>2.0</version>        <executions>            <execution>                <phase>package</phase>                <goals>                    <goal>shade</goal>                </goals>                <configuration>                    <finalName>microbenchmarks</finalName>                    <transformers>                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">                            <mainClass>org.openjdk.jmh.Main</mainClass>                        </transformer>                    </transformers>                </configuration>            </execution>        </executions>    </plugin>123456789101112131415161718192021222324252627282930313233然後,就可以著手寫第一個JMH例子了:
package co.speedar.infra.test;import java.util.concurrent.TimeUnit;import org.openjdk.jmh.annotations.Benchmark;import org.openjdk.jmh.annotations.BenchmarkMode;import org.openjdk.jmh.annotations.Mode;import org.openjdk.jmh.annotations.OutputTimeUnit;import org.openjdk.jmh.annotations.Scope;import org.openjdk.jmh.annotations.State;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 org.slf4j.Logger;import org.slf4j.LoggerFactory;@BenchmarkMode(Mode.AverageTime) // 測試方法平均執行時間@OutputTimeUnit(TimeUnit.MICROSECONDS) // 輸出結果的時間粒度為微秒@State(Scope.Thread) // 每個測試線程一個實例public class FirstBenchMark {    private static Logger log = LoggerFactory.getLogger(FirstBenchMark.class);    @Benchmark    public String stringConcat() {        String a = "a";        String b = "b";        String c = "c";        String s = a + b + c;        log.debug(s);        return s;    }    public static void main(String[] args) throws RunnerException {        // 使用一個單獨進程執行測試,執行5遍warmup,然後執行5遍測試        Options opt = new OptionsBuilder().include(FirstBenchMark.class.getSimpleName()).forks(1).warmupIterations(5)                .measurementIterations(5).build();        new Runner(opt).run();    }}1234567891011121314151617181920212223242526272829303132333435在上面的測試代碼中,加了幾個類註解以及一個方法註解,在main方法中指明瞭測試的一些選項,然後使用JMH提供的Runner執行測試。在註釋中提供了大致的講解,具體的選項說明後邊再詳述。接下來我們直接跑起來這個測試看看結果如何。執行測試,可能會遇到報錯: Exception in thread "main" java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList 解決方法:
先執行mvn clean install然後再在ide中執行main方法;或者在eclipse中安裝m2e-apt插件,然後啟用Automatically configure JDT APT選項;  

然後,就可以愉快地看到測試結果如下:
# JMH 1.14.1 (released 525 days ago, please consider updating!)# VM version: JDK 1.8.0_91, VM 25.91-b14# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/bin/java# VM options: -Dfile.encoding=UTF-8# Warmup: 5 iterations, 1 s each# Measurement: 5 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Average time, time/op# Benchmark: co.speedar.infra.test.FirstBenchMark.stringConcat# Run progress: 0.00% complete, ETA 00:00:10# Fork: 1 of 1# Warmup Iteration   1: 0.009 us/op# Warmup Iteration   2: 0.011 us/op# Warmup Iteration   3: 0.007 us/op# Warmup Iteration   4: 0.006 us/op# Warmup Iteration   5: 0.006 us/opIteration   1: 0.006 us/opIteration   2: 0.005 us/opIteration   3: 0.005 us/opIteration   4: 0.006 us/opIteration   5: 0.006 us/op
Result "stringConcat":  0.006 ±(99.9%) 0.001 us/op [Average]  (min, avg, max) = (0.005, 0.006, 0.006), stdev = 0.001  CI (99.9%): [0.005, 0.006] (assumes normal distribution)
# Run complete. Total time: 00:00:10Benchmark                    Mode  Cnt  Score    Error  UnitsFirstBenchMark.stringConcat  avgt    5  0.006 ±  0.001  us/op12345678910111213141516171819202122232425262728293031測試結果表明,被測試方法平均耗時為0.006微秒,誤差為±0.001微秒。
三、詳細說明3.1 基本概念首先看看JMH的幾個基本概念:
Mode Mode 表示 JMH 進行 Benchmark 時所使用的模式。通常是測量的維度不同,或是測量的方式不同。目前 JMH 共有四種模式:
Throughput: 整體吞吐量,例如“1秒內可以執行多少次調用”。
AverageTime: 調用的平均時間,例如“每次調用平均耗時xxx毫秒”。
SampleTime: 隨機取樣,最後輸出取樣結果的分佈,例如“99%的調用在xxx毫秒以內,99.99%的調用在xxx毫秒以內”
SingleShotTime: 以上模式都是預設一次 iteration 是 1s,唯有 SingleShotTime 是只運行一次。往往同時把 warmup 次數設為0,用於測試冷啟動時的性能。
Iteration Iteration 是 JMH 進行測試的最小單位。在大部分模式下,一次 iteration 代表的是一秒,JMH 會在這一秒內不斷調用需要 benchmark 的方法,然後根據模式對其採樣,計算吞吐量,計算平均執行時間等。
Warmup
Warmup 是指在實際進行 benchmark 前先進行預熱的行為。為什麼需要預熱?因為 JVM 的 JIT 機制的存在,如果某個函數被調用多次之後,JVM 會嘗試將其編譯成為機器碼從而提高執行速度。為了讓 benchmark 的結果更加接近真實情況就需要進行預熱。
3.2 註解與選項3.2.1 常用註解說明@BenchmarkMode 對應Mode選項,可用於類或者方法上, 需要註意的是,這個註解的value是一個數組,可以把幾種Mode集合在一起執行,還可以設置為Mode.All,即全部執行一遍。
@State 類註解,JMH測試類必須使用@State註解,State定義了一個類實例的生命周期,可以類比Spring Bean的Scope。由於JMH允許多線程同時執行測試,不同的選項含義如下:
Scope.Thread:預設的State,每個測試線程分配一個實例;
Scope.Benchmark:所有測試線程共用一個實例,用於測試有狀態實例在多線程共用下的性能;
Scope.Group:每個線程組共用一個實例;
@OutputTimeUnit benchmark 結果所使用的時間單位,可用於類或者方法註解,使用java.util.concurrent.TimeUnit中的標準時間單位。
@Benchmark 方法註解,表示該方法是需要進行 benchmark 的對象。
@Setup 方法註解,會在執行 benchmark 之前被執行,正如其名,主要用於初始化。
@TearDown 方法註解,與@Setup 相對的,會在所有 benchmark 執行結束以後執行,主要用於資源的回收等。
@Param 成員註解,可以用來指定某項參數的多種情況。特別適合用來測試一個函數在不同的參數輸入的情況下的性能。@Param註解接收一個String數組,在@setup方法執行前轉化為為對應的數據類型。多個@Param註解的成員之間是乘積關係,譬如有兩個用@Param註解的欄位,第一個有5個值,第二個欄位有2個值,那麼每個測試方法會跑5*2=10次。
3.2.2 註解使用例子以下示例代碼來自JMH官方例子,為了節省篇幅刪除了頭部的license聲明和重覆的註釋。
@BenchmarkMode和@OutputTimeUnitpublic class JMHSample_02_BenchmarkModes {    @Benchmark    @BenchmarkMode(Mode.Throughput)    @OutputTimeUnit(TimeUnit.SECONDS)    public void measureThroughput() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * Mode.AverageTime measures the average execution time, and it does it     * in the way similar to Mode.Throughput.     *     * Some might say it is the reciprocal throughput, and it really is.     * There are workloads where measuring times is more convenient though.     */    @Benchmark    @BenchmarkMode(Mode.AverageTime)    @OutputTimeUnit(TimeUnit.MICROSECONDS)    public void measureAvgTime() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * Mode.SampleTime samples the execution time. With this mode, we are     * still running the method in a time-bound iteration, but instead of     * measuring the total time, we measure the time spent in *some* of     * the benchmark method calls.     *     * This allows us to infer the distributions, percentiles, etc.     *     * JMH also tries to auto-adjust sampling frequency: if the method     * is long enough, you will end up capturing all the samples.     */    @Benchmark    @BenchmarkMode(Mode.SampleTime)    @OutputTimeUnit(TimeUnit.MICROSECONDS)    public void measureSamples() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * Mode.SingleShotTime measures the single method invocation time. As the Javadoc     * suggests, we do only the single benchmark method invocation. The iteration     * time is meaningless in this mode: as soon as benchmark method stops, the     * iteration is over.     *     * This mode is useful to do cold startup tests, when you specifically     * do not want to call the benchmark method continuously.     */    @Benchmark    @BenchmarkMode(Mode.SingleShotTime)    @OutputTimeUnit(TimeUnit.MICROSECONDS)    public void measureSingleShot() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * We can also ask for multiple benchmark modes at once. All the tests     * above can be replaced with just a single test like this:     */    @Benchmark    @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime})    @OutputTimeUnit(TimeUnit.MICROSECONDS)    public void measureMultiple() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * Or even...     */    @Benchmark    @BenchmarkMode(Mode.All)    @OutputTimeUnit(TimeUnit.MICROSECONDS)    public void measureAll() throws InterruptedException {        TimeUnit.MILLISECONDS.sleep(100);    }    /*     * ============================== HOW TO RUN THIS TEST: ====================================     *     * You are expected to see the different run modes for the same benchmark.     * Note the units are different, scores are consistent with each other.     *     * You can run this test:     *     * a) Via the command line:     *    $ mvn clean install     *    $ java -jar target/benchmarks.jar JMHSample_02 -wi 5 -i 5 -f 1     *    (we requested 5 warmup/measurement iterations, single fork)     *     * b) Via the Java API:     *    (see the JMH homepage for possible caveats when running from IDE:     *      http://openjdk.java.net/projects/code-tools/jmh/)     */    public static void main(String[] args) throws RunnerException {        Options opt = new OptionsBuilder()                .include(JMHSample_02_BenchmarkModes.class.getSimpleName())                .warmupIterations(5)                .measurementIterations(5)                .forks(1)                .build();        new Runner(opt).run();    }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798@Statepublic class JMHSample_03_States {    @State(Scope.Benchmark)    public static class BenchmarkState {        volatile double x = Math.PI;    }    @State(Scope.Thread)    public static class ThreadState {        volatile double x = Math.PI;    }    /*     * Benchmark methods can reference the states, and JMH will inject the     * appropriate states while calling these methods. You can have no states at     * all, or have only one state, or have multiple states referenced. This     * makes building multi-threaded benchmark a breeze.     *     * For this exercise, we have two methods.     */    @Benchmark    public void measureUnshared(ThreadState state) {        // All benchmark threads will call in this method.        //        // However, since ThreadState is the Scope.Thread, each thread        // will have it's own copy of the state, and this benchmark        // will measure unshared case.        state.x++;    }    @Benchmark    public void measureShared(BenchmarkState state) {        // All benchmark threads will call in this method.        //        // Since BenchmarkState is the Scope.Benchmark, all threads        // will share the state instance, and we will end up measuring        // shared case.        state.x++;    }
    public static void main(String[] args) throws RunnerException {        Options opt = new OptionsBuilder()                .include(JMHSample_03_States.class.getSimpleName())                .warmupIterations(5)                .measurementIterations(5)                .threads(4)                .forks(1)                .build();        new Runner(opt).run();    }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647@Param@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Fork(1)@State(Scope.Benchmark)public class JMHSample_27_Params {    /**     * In many cases, the experiments require walking the configuration space     * for a benchmark. This is needed for additional control, or investigating     * how the workload performance changes with different settings.     */    @Param({"1", "31", "65", "101", "103"})    public int arg;    @Param({"0", "1", "2", "4", "8", "16", "32"})    public int certainty;    @Benchmark    public boolean bench() {        return BigInteger.valueOf(arg).isProbablePrime(certainty);    }    public static void main(String[] args) throws RunnerException {        Options opt = new OptionsBuilder()                .include(JMHSample_27_Params.class.getSimpleName())//                .param("arg", "41", "42") // Use this to selectively constrain/override parameters                .build();        new Runner(opt).run();    }}12345678910111213141516171819202122232425262728293.2.3 常用選項說明include benchmark 所在的類的名字,這裡可以使用正則表達式對所有類進行匹配。
fork JVM因為使用了profile-guided optimization而“臭名昭著”,這對於微基準測試來說十分不友好,因為不同測試方法的profile混雜在一起,“互相傷害”彼此的測試結果。對於每個@Benchmark方法使用一個獨立的進程可以解決這個問題,這也是JMH的預設選項。註意不要設置為0,設置為n則會啟動n個進程執行測試(似乎也沒有太大意義)。fork選項也可以通過方法註解以及啟動參數來設置。
warmupIterations 預熱的迭代次數,預設1秒。
measurementIterations 實際測量的迭代次數,預設1秒。
CompilerControl 可以在@Benchmark註解中指定編譯器行為。
CompilerControl.Mode.DONT_INLINE:This method should not be inlined. Useful to measure the method call cost and to evaluate if it worth to increase the inline threshold for the JVM.CompilerControl.Mode.INLINE:Ask the compiler to inline this method. Usually should be used in conjunction with Mode.DONT_INLINE to check pros and cons of inlining.CompilerControl.Mode.EXCLUDE:Do not compile this method – interpret it instead. Useful in holy wars as an argument how good is the JIT.Group 方法註解,可以把多個 benchmark 定義為同一個 group,則它們會被同時執行,譬如用來模擬生產者-消費者讀寫速度不一致情況下的表現。可以參考如下例子: CounterBenchmark.java
Level 用於控制 @Setup,@TearDown 的調用時機,預設是 Level.Trial。
Trial:每個benchmark方法前後;
Iteration:每個benchmark方法每次迭代前後;
Invocation:每個benchmark方法每次調用前後,謹慎使用,需留意javadoc註釋;
Threads 每個fork進程使用多少條線程去執行你的測試方法,預設值是Runtime.getRuntime().availableProcessors()。
四、一些值得註意的地方4.1 無用代碼消除(Dead Code Elimination)現代編譯器是十分聰明的,它們會對你的代碼進行推導分析,判定哪些代碼是無用的然後進行去除,這種行為對微基準測試是致命的,它會使你無法準確測試出你的方法性能。JMH本身已經對這種情況做了處理,你只要記住:1.永遠不要寫void方法;2.在方法結束返回你的計算結果。有時候如果需要返回多於一個結果,可以考慮自行合併計算結果,或者使用JMH提供的BlackHole對象:
/* * This demonstrates Option A: * * Merge multiple results into one and return it. * This is OK when is computation is relatively heavyweight, and merging * the results does not offset the results much. */@Benchmarkpublic double measureRight_1() {    return Math.log(x1) + Math.log(x2);}/* * This demonstrates Option B: * * Use explicit Blackhole objects, and sink the values there. * (Background: Blackhole is just another @State object, bundled with JMH). */@Benchmarkpublic void measureRight_2(Blackhole bh) {    bh.consume(Math.log(x1));    bh.consume(Math.log(x2));}123456789101112131415161718192021224.2 常量摺疊(Constant Folding)常量摺疊是一種現代編譯器優化策略,例如,i = 320 * 200 * 32,多數的現代編譯器不會真的產生兩個乘法的指令再將結果儲存下來,取而代之的,他們會辨識出語句的結構,併在編譯時期將數值計算出來(i = 2,048,000)。
在微基準測試中,如果你的計算輸入是可預測的,也不是一個@State實例變數,那麼很可能會被JIT給優化掉。對此,JMH的建議是:1.永遠從@State實例中讀取你的方法輸入;2.返回你的計算結果;3.或者考慮使用BlackHole對象;
見如下官方例子:
@State(Scope.Thread)@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)public class JMHSample_10_ConstantFold {    private double x = Math.PI;    private final double wrongX = Math.PI;    @Benchmark    public double baseline() {        // simply return the value, this is a baseline        return Math.PI;    }    @Benchmark    public double measureWrong_1() {        // This is wrong: the source is predictable, and computation is foldable.        return Math.log(Math.PI);    }    @Benchmark    public double measureWrong_2() {        // This is wrong: the source is predictable, and computation is foldable.        return Math.log(wrongX);    }    @Benchmark    public double measureRight() {        // This is correct: the source is not predictable.        return Math.log(x);    }    public static void main(String[] args) throws RunnerException {        Options opt = new OptionsBuilder()                .include(JMHSample_10_ConstantFold.class.getSimpleName())                .warmupIterations(5)                .measurementIterations(5)                .forks(1)                .build();        new Runner(opt).run();    }}1234567891011121314151617181920212223242526272829303132333435364.3 迴圈展開(Loop Unwinding)迴圈展開最常用來降低迴圈開銷,為具有多個功能單元的處理器提供指令級並行。也有利於指令流水線的調度。例如:
for (i = 1; i <= 60; i++)    a[i] = a[i] * b + c;12可以展開成:
for (i = 1; i <= 60; i+=3){  a[i] = a[i] * b + c;  a[i+1] = a[i+1] * b + c;  a[i+2] = a[i+2] * b + c;}123456由於編譯器可能會對你的代碼進行迴圈展開,因此JMH建議不要在你的測試方法中寫任何迴圈。如果確實需要執行迴圈計算,可以結合@BenchmarkMode(Mode.SingleShotTime)和@Measurement(batchSize = N)來達到同樣的效果。參考如下例子:
/* * Suppose we want to measure how much it takes to sum two integers: */int x = 1;int y = 2;/* * This is what you do with JMH. */@Benchmark@OperationsPerInvocation(100)public int measureRight() {    return (x + y);}12345678910111213還有這個例子:
@State(Scope.Thread)@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Fork(3)@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)public class JMHSample_34_SafeLooping {    /*     * JMHSample_11_Loops warns about the dangers of using loops in @Benchmark methods.     * Sometimes, however, one needs to traverse through several elements in a dataset.     * This is hard to do without loops, and therefore we need to devise a scheme for     * safe looping.     */    /*     * Suppose we want to measure how much it takes to execute work() with different     * arguments. This mimics a frequent use case when multiple instances with the same     * implementation, but different data, is measured.     */    static final int BASE = 42;    static int work(int x) {        return BASE + x;    }    /*     * Every benchmark requires control. We do a trivial control for our benchmarks     * by checking the benchmark costs are growing linearly with increased task size.     * If it doesn't, then something wrong is happening.     */    @Param({"1", "10", "100", "1000"})    int size;    int[] xs;    @Setup    public void setup() {        xs = new int[size];        for (int c = 0; c < size; c++) {            xs[c] = c;        }    }    /*     * First, the obviously wrong way: "saving" the result into a local variable would not     * work. A sufficiently smart compiler will inline work(), and figure out only the last     * work() call needs to be evaluated. Indeed, if you run it with varying $size, the score     * will stay the same!     */    @Benchmark    public int measureWrong_1() {        int acc = 0;        for (int x : xs) {            acc = work(x);        }        return acc;    }    /*     * Second, another wrong way: "accumulating" the result into a local variable. While     * it would force the computation of each work() method, there are software pipelining     * effects in action, that can merge the operations between two otherwise distinct work()     * bodies. This will obliterate the benchmark setup.     *     * In this example, HotSpot does the unrolled loop, merges the $BASE operands into a single     * addition to $acc, and then does a bunch of very tight stores of $x-s. The final performance     * depends on how much of the loop unrolling happened *and* how much data is available to make     * the large strides.     */    @Benchmark    public int measureWrong_2() {        int acc = 0;        for (int x : xs) {            acc += work(x);        }        return acc;    }    /*     * Now, let's see how to measure these things properly. A very straight-forward way to     * break the merging is to sink each result to Blackhole. This will force runtime to compute     * every work() call in full. (We would normally like to care about several concurrent work()     * computations at once, but the memory effects from Blackhole.consume() prevent those optimization     * on most runtimes).     */    @Benchmark    public void measureRight_1(Blackhole bh) {        for (int x : xs) {            bh.consume(work(x));        }    }    /*     * DANGEROUS AREA, PLEASE READ THE DESCRIPTION BELOW.     *     * Sometimes, the cost of sinking the value into a Blackhole is dominating the nano-benchmark score.     * In these cases, one may try to do a make-shift "sinker" with non-inlineable method. This trick is     * *very* VM-specific, and can only be used if you are verifying the generated code (that's a good     * strategy when dealing with nano-benchmarks anyway).     *     * You SHOULD NOT use this trick in most cases. Apply only where needed.     */    @Benchmark    public void measureRight_2() {        for (int x : xs) {            sink(work(x));        }    }    @CompilerControl(CompilerControl.Mode.DONT_INLINE)    public static void sink(int v) {        // IT IS VERY IMPORTANT TO MATCH THE SIGNATURE TO AVOID AUTOBOXING.        // The method intentionally does nothing.    }
    public static void main(String[] args) throws RunnerException {        Options opt = new OptionsBuilder()                .include(JMHSample_34_SafeLooping.class.getSimpleName())                .warmupIterations(5)                .measurementIterations(5)                .forks(3)                .build();        new Runner(opt).run();    }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115五、License聲明文中大部分例子來自JMH官方的實例工程:jmh-samples,基於節省篇幅考慮去掉了頭部的license聲明,現補充如下:
/* * Copyright (c) 2014, Oracle America, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * *  * Redistributions of source code must retain the above copyright notice, *    this list of conditions and the following disclaimer. * *  * Redistributions in binary form must reproduce the above copyright *    notice, this list of conditions and the following disclaimer in the *    documentation and/or other materials provided with the distribution. * *  * Neither the name of Oracle nor the names of its contributors may be used *    to endorse or promote products derived from this software without *    specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */--------------------- 作者:秦沙 來源:CSDN 原文:https://blog.csdn.net/lxbjkben/article/details/79410740 版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 關於精度: 取整 除法取整: (除數為正)被除數為正時系統除法為向下取整,被除數為負時系統除法為向上取整。 向上取整(被除數非負,除數為正): 一般寫法(有bug): 上述寫法只適用於x為正的情況,x為0時有錯誤。 正確寫法: 或 庫函數(cmath庫) : (返回值為double) 向上取整 ...
  • 上午剛到公司,準備開始一天的摸魚之旅時突然收到了一封監控中心的郵件。 心中暗道不好,因為監控系統從來不會告訴我應用完美無 bug,其實系統挺猥瑣。 打開郵件一看,果然告知我有一個應用的線程池隊列達到閾值觸發了報警。 ...
  • 最近看了很多我周圍的同學,也都是剛開始接觸Eclipse,但是都頭疼於eclipse的漢化問題。 好在的是,Eclipse的漢化比較簡單,不用到網上自己下載漢化包,而且關於這個軟體的漢化也非常的多,所以我也就寫一下我這邊的Eclipse版本的漢化。 步驟 版本:Eclipse Photon(2018 ...
  • 恢復內容開始 在Spring IOC模塊中Bean是非常重要的。在這裡我想給大家講講關於Bean對象實例化的三種註入方式: 首先,我先講一下關於Bean對象屬性值的兩種註入方式:set註入 和 構造註入 constructor-arg:通過構造函數註入。 property:通過setter對應的方法 ...
  • 這個demo是基於springboot項目的。 名詞介紹: ShiroShiro 主要分為 安全認證 和 介面授權 兩個部分,其中的核心組件為 Subject、 SecurityManager、 Realms,公共部分 Shiro 都已經為我們封裝好了,我們只需要按照一定的規則去編寫響應的代碼即可… ...
  • 函數 函數的定義 1.什麼是函數? 函數就是定義在類中的具有特定功能的一段獨立小程式。函數也稱為方法。 2.函數的格式: 修飾符 返回值類型 函數名(參數類型 形式參數1,參數類型 形式參數2,.. ) { 執行語句; return返回值; } 返回值類型:函數運行後的結果的數據類型。 參數類型:是 ...
  • 進程、線程 ​ 進程(Process) 是程式的運行實例。例如,一個運行的 Eclipse 就是一個進程。進程是程式向操作系統申請資源(如記憶體空間和文件句柄)的基本單位。線程(Thread)是進程中可獨立執行的最小單位。一個進程可以包含多個線程。進程和線程的關係,好比一個營業中的飯店與其正在工作的員 ...
  • about 演算法 項目介紹 工作之餘,代碼敲多了,停下來思考思考,會有異常不到的收穫。。。只為更好的自己 如何用棧實現隊列? 提示下:用一個棧肯定是沒辦法實現隊列,但如果我們有兩個棧呢? 分析:棧和隊列的特性 棧是先進後出,FILO 出入元素都是在同一端(棧頂) 入棧 1540432924606.p ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...