国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

性能調(diào)優(yōu)之JMH必知必會(huì)3:編寫正確的微基準(zhǔn)測試用例

這篇具有很好參考價(jià)值的文章主要介紹了性能調(diào)優(yōu)之JMH必知必會(huì)3:編寫正確的微基準(zhǔn)測試用例。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。


?

JMH必知必會(huì)系列文章(持續(xù)更新)

  • 性能調(diào)優(yōu)之JMH必知必會(huì)1:什么是JMH
  • 性能調(diào)優(yōu)之JMH必知必會(huì)2:JMH的基本用法
  • 性能調(diào)優(yōu)之JMH必知必會(huì)4:JMH的高級(jí)用法
  • 性能調(diào)優(yōu)之JMH必知必會(huì)5:JMH的Profiler

?文章來源地址http://www.zghlxwxcb.cn/news/detail-403731.html

一、前言

?
??在前面兩篇文章中分別介紹了什么是JMH、JMH的基本法?,F(xiàn)在來介紹JMH正確的微基準(zhǔn)測試用例如何編寫?!?code>單位換算:1秒(s)=1000000微秒(us)=1000000000納秒(ns)】
?
??官方JMH源碼(包含樣例,在jmh-samples包里)下載地址:https://github.com/openjdk/jmh/tags。
?
??官方JMH樣例在線瀏覽地址:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/。
?
??本文內(nèi)容參考書籍《Java高并發(fā)編程詳解:深入理解并發(fā)核心庫》,作者為 汪文君 ,讀者有需要可以去購買正版書籍。
?
??本文由 @大白有點(diǎn)菜 原創(chuàng),請(qǐng)勿盜用,轉(zhuǎn)載請(qǐng)說明出處!如果覺得文章還不錯(cuò),請(qǐng)點(diǎn)點(diǎn)贊,加關(guān)注,謝謝!
?

二、編寫正確的微基準(zhǔn)測試用例

1、添加JMH依賴包

??在Maven倉庫中搜索依賴包jmh-corejmh-generator-annprocess ,版本為 1.36 。需要注釋 jmh-generator-annprocess 包中的“<scope>test</scope>”,不然項(xiàng)目運(yùn)行會(huì)報(bào)錯(cuò)。

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.36</version>
</dependency>

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

2、避免DCE(Dead Code Elimination)

??所謂Dead Code Elimination是指JVM為我們擦去了一些上下文無關(guān),甚至經(jīng)過計(jì)算之后確定壓根不會(huì)用到的代碼,如下面的代碼塊。

public void test(){
    int x=10;
    int y=10;
    int z=x+y;
}

??我們?cè)趖est方法中分別定義了x和y,并且經(jīng)過相加運(yùn)算得到了z,但是在該方法的下文中再也沒有其他地方使用到z(既沒有對(duì)z進(jìn)行返回,也沒有對(duì)其進(jìn)行二次使用,z甚至不是一個(gè)全局的變量),JVM很有可能會(huì)將test()方法當(dāng)作一個(gè)空的方法來看待,也就是說會(huì)擦除對(duì)x、y的定義,以及計(jì)算z的相關(guān)代碼。
?
【驗(yàn)證在Java代碼的執(zhí)行過程中虛擬機(jī)是否會(huì)擦除與上下文無關(guān)的代碼 - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(避免DCE,即死碼消除)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_DCE {

    @Benchmark
    public void baseline(){
        // 空的方法
    }

    @Benchmark
    public void measureLog1(){
        // 進(jìn)行數(shù)學(xué)運(yùn)算,但是在局部方法內(nèi)
        Math.log(Math.PI);
    }

    @Benchmark
    public void measureLog2(){
        // result是通過數(shù)學(xué)運(yùn)算所得并且在下一行代碼中得到了使用
        double result = Math.log(Math.PI);
        // 對(duì)result進(jìn)行數(shù)學(xué)運(yùn)算,但是結(jié)果既不保存也不返回,更不會(huì)進(jìn)行二次運(yùn)算
        Math.log(result);
    }

    @Benchmark
    public double measureLog3(){
        // 返回?cái)?shù)學(xué)運(yùn)算結(jié)果
        return Math.log(Math.PI);
    }

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

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

?
【驗(yàn)證在Java代碼的執(zhí)行過程中虛擬機(jī)是否會(huì)擦除與上下文無關(guān)的代碼 - 代碼運(yùn)行結(jié)果】

Benchmark                                               Mode  Cnt   Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.baseline     avgt    510??           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog1  avgt    510??           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog2  avgt    510??           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog3  avgt    5   0.002 ±  0.001  us/op
  • baseline 方法作為一個(gè)空的方法,主要用于做基準(zhǔn)數(shù)據(jù)。
  • measureLog1 中雖然進(jìn)行了 log 運(yùn)算,但是結(jié)果既沒有再進(jìn)行二次使用,也沒有進(jìn)行返回。
  • measureLog2 中同樣進(jìn)行了 log 運(yùn)算,雖然第一次的運(yùn)算結(jié)果是作為第二次入?yún)硎褂玫模堑诙螆?zhí)行結(jié)束后也再?zèng)]有對(duì)其有更進(jìn)一步的使用。
  • measureLog3 方法與 measureLog1 的方法類似,但是該方法對(duì)運(yùn)算結(jié)果進(jìn)行了返回操作。

??從輸出結(jié)果看出,measureLog1 和 measureLog2 方法的基準(zhǔn)性能與 baseline 幾乎完全一致,因此我們可以肯定這兩個(gè)方法中的代碼進(jìn)行過擦除操作,這樣的代碼被稱為 Dead Code(死代碼,其他地方都沒有用到的代碼片段),而 measureLog3 則與上述兩個(gè)方法不同,由于它對(duì)結(jié)果進(jìn)行了返回,因此 Math.log(PI) 不會(huì)被認(rèn)為它是 Dead Code,因此它將占用一定的CPU時(shí)間。
?
??得出結(jié)論:要想編寫性能良好的微基準(zhǔn)測試方法,不要讓方法存在Dead Code,最好每一個(gè)基準(zhǔn)測試方法都有返回值
?
【附上官方Dead Code樣例(JMHSample_08_DeadCode) - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:官方Dead Code樣例
 * @author 大白有點(diǎn)菜
 */
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JmhTestApp14_DeadCode {

    /**
     * The downfall of many benchmarks is Dead-Code Elimination (DCE): compilers
     * are smart enough to deduce some computations are redundant and eliminate
     * them completely. If the eliminated part was our benchmarked code, we are
     * in trouble.
     *
     * 許多基準(zhǔn)測試的失敗是死代碼消除(DCE):編譯器足夠聰明,可以推斷出一些計(jì)算是多余的,并完全消除它們。
     * 如果被淘汰的部分是我們的基準(zhǔn)代碼,我們就有麻煩了。
     *
     * Fortunately, JMH provides the essential infrastructure to fight this
     * where appropriate: returning the result of the computation will ask JMH
     * to deal with the result to limit dead-code elimination (returned results
     * are implicitly consumed by Blackholes, see JMHSample_09_Blackholes).
     *
     * 幸運(yùn)的是,JMH 提供了必要的基礎(chǔ)設(shè)施來在適當(dāng)?shù)臅r(shí)候解決這個(gè)問題:返回計(jì)算結(jié)果將要求 JMH 處理結(jié)果
     * 以限制死代碼消除(返回的結(jié)果被黑洞隱式消耗,請(qǐng)參閱 JMHSample_09_Blackholes)。
     */

    private double x = Math.PI;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    @Benchmark
    public void baseline() {
        // do nothing, this is a baseline
    }

    @Benchmark
    public void measureWrong() {
        // This is wrong: result is not used and the entire computation is optimized away.
        compute(x);
    }

    @Benchmark
    public double measureRight() {
        // This is correct: the result is being used.
        return compute(x);
    }

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

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

?
【附上官方Dead Code樣例(JMHSample_08_DeadCode) - 代碼運(yùn)行結(jié)果】

Benchmark                           Mode  Cnt   Score   Error  Units
JmhTestApp14_DeadCode.baseline      avgt    5   0.261 ± 0.027  ns/op
JmhTestApp14_DeadCode.measureRight  avgt    5  13.920 ± 0.591  ns/op
JmhTestApp14_DeadCode.measureWrong  avgt    5   0.266 ± 0.039  ns/op

?
【官方Dead Code樣例(JMHSample_08_DeadCode)注解 - 谷歌和百度翻譯互補(bǔ)】

??許多基準(zhǔn)測試的失敗是死代碼消除(DCE):編譯器足夠聰明,可以推斷出一些計(jì)算是多余的,并完全消除它們。如果被淘汰的部分是我們的基準(zhǔn)代碼,我們就有麻煩了。
?
??幸運(yùn)的是,JMH 提供了必要的基礎(chǔ)設(shè)施來在適當(dāng)?shù)臅r(shí)候解決這個(gè)問題:返回計(jì)算結(jié)果將要求 JMH 處理結(jié)果以限制死代碼消除(返回的結(jié)果被黑洞隱式消耗,請(qǐng)參閱 JMHSample_09_Blackholes)。

?

3、使用Balckhole

??假設(shè)在基準(zhǔn)測試方法中,需要將兩個(gè)計(jì)算結(jié)果作為返回值,那么我們?cè)撊绾稳プ瞿??我們第一時(shí)間想到的可能是將結(jié)果存放到某個(gè)數(shù)組或者容器當(dāng)中作為返回值,但是這種對(duì)數(shù)組或者容器的操作會(huì)對(duì)性能統(tǒng)計(jì)造成干擾,因?yàn)閷?duì)數(shù)組或者容器的寫操作也是需要花費(fèi)一定的CPU時(shí)間的。
?
??JMH提供了一個(gè) Blackhole(黑洞) 類,可以在不作任何返回的情況下避免 Dead Code 的發(fā)生,與Linux系統(tǒng)下的黑洞設(shè)備 /dev/null 非常相似。
?
【Blackhole樣例 - 代碼】

package cn.zhuangyt.javabase.jmh;

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

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(使用Blackhole,即黑洞)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole {

    double x1 = Math.PI;
    double x2 = Math.PI * 2;

    @Benchmark
    public double baseline()
    {
        // 不是Dead Code,因?yàn)閷?duì)結(jié)果進(jìn)行了返回
        return Math.pow(x1, 2);
    }

    @Benchmark
    public double powButReturnOne()
    {
        // Dead Code會(huì)被擦除
        Math.pow(x1, 2);
        // 不會(huì)被擦除,因?yàn)閷?duì)結(jié)果進(jìn)行了返回
        return Math.pow(x2, 2);
    }

    @Benchmark
    public double powThenAdd()
    {
        // 通過加法運(yùn)算對(duì)兩個(gè)結(jié)果進(jìn)行了合并,因此兩次的計(jì)算都會(huì)生效
        return Math.pow(x1, 2) + Math.pow(x2, 2);
    }

    @Benchmark
    public void useBlackhole(Blackhole hole)
    {
        // 將結(jié)果存放至black hole中,因此兩次pow操作都會(huì)生效
        hole.consume(Math.pow(x1, 2));
        hole.consume(Math.pow(x2, 2));
    }

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

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

?
【Blackhole樣例 - 代碼運(yùn)行結(jié)果】

Benchmark                                                             Mode  Cnt  Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.baseline         avgt    5  2.126 ± 0.163  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.powButReturnOne  avgt    5  2.065 ± 0.112  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.powThenAdd       avgt    5  2.181 ± 0.151  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.useBlackhole     avgt    5  3.748 ± 0.342  ns/op
  • baseline 方法中對(duì) x1 進(jìn)行了 pow 運(yùn)算,之后返回,因此這個(gè)基準(zhǔn)測試方法是非常合理的。
  • powButReturnOne 方法中的第一個(gè) pow 運(yùn)算仍然避免不了被當(dāng)作 Dead Code 的命運(yùn),因此我們很難得到兩次 pow 計(jì)算的方法耗時(shí),但是對(duì) x2 的 pow 運(yùn)算會(huì)作為返回值返回,因此不是 Dead Code。
  • powThenAdd 方法就比較聰明,它同樣會(huì)有返回值,兩次 pow 操作也會(huì)被正常執(zhí)行,但是由于采取的是加法運(yùn)算,因此相加操作的CPU耗時(shí)也被計(jì)算到了兩次 pow 操作中。
  • useBlackhole 方法中兩次 pow 方法都會(huì)被執(zhí)行,但是我們并沒有對(duì)其進(jìn)行返回操作,而是將其寫入了 black hole 之中。

??輸出結(jié)果表明,baseline 和 putButReturnOne 方法的性能幾乎是一樣的,powThenAdd 的性能相比前兩個(gè)方法占用CPU的時(shí)間要稍微長一些,原因是該方法執(zhí)行了兩次 pow 操作。在 useBlackhole 中雖然沒有對(duì)兩個(gè)參數(shù)進(jìn)行任何的合并操作,但是由于執(zhí)行了 black hole 的 consume 方法,因此也會(huì)占用一定的CPU資源。雖然 blackhole 的 consume 方法會(huì)占用一定的CPU資源,但是如果在無返回值的基準(zhǔn)測試方法中針對(duì)局部變量的使用都統(tǒng)一通過 blackhole 進(jìn)行 consume ,那么就可以確保同樣的基準(zhǔn)執(zhí)行條件,就好比拳擊比賽時(shí),對(duì)抗的拳手之間需要統(tǒng)一的體重量級(jí)一樣。
?
??得出結(jié)論:Blackhole 可以幫助你在無返回值的基準(zhǔn)測試方法中避免DC(Dead Code)情況的發(fā)生。
?
【附上官方Blackhole樣例(JMHSample_09_Blackholes) - 代碼】

package cn.zhuangyt.javabase.jmh;

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

/**
 * JMH測試14:官方Blackhole樣例
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Balckhole {

    /**
     * Should your benchmark require returning multiple results, you have to
     * consider two options (detailed below).
     *
     * 如果您的基準(zhǔn)測試需要返回多個(gè)結(jié)果,您必須考慮兩個(gè)選項(xiàng)(詳細(xì)信息如下)。
     *
     * NOTE: If you are only producing a single result, it is more readable to
     * use the implicit return, as in JMHSample_08_DeadCode. Do not make your benchmark
     * code less readable with explicit Blackholes!
     *
     * 注意:如果您只生成一個(gè)結(jié)果,使用隱式返回更具可讀性,如 JMHSample_08_DeadCode。不要使用顯式黑洞來降低基準(zhǔn)代碼的可讀性!
     */

    double x1 = Math.PI;
    double x2 = Math.PI * 2;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    /**
     * Baseline measurement: how much a single compute() costs.
     *
     * 基線測量:單個(gè) compute() 的成本是多少。
     */

    @Benchmark
    public double baseline() {
        return compute(x1);
    }

    /**
     * While the compute(x2) computation is intact, compute(x1)
     * is redundant and optimized out.
     *
     * 雖然 compute(x2) 計(jì)算完好無損,但 compute(x1) 是多余的并經(jīng)過優(yōu)化。
     *
     */

    @Benchmark
    public double measureWrong() {
        compute(x1);
        return compute(x2);
    }

    /**
     * This demonstrates Option A:
     *
     * 這演示了選項(xiàng) 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.
     *
     * 將多個(gè)結(jié)果合并為一個(gè)并返回。當(dāng)計(jì)算相對(duì)重量級(jí)時(shí),這是可以的,并且合并結(jié)果不會(huì)抵消太多結(jié)果。
     */

    @Benchmark
    public double measureRight_1() {
        return compute(x1) + compute(x2);
    }

    /**
     * This demonstrates Option B:
     *
     * 這演示了選項(xiàng) B:
     *
     * Use explicit Blackhole objects, and sink the values there.
     * (Background: Blackhole is just another @State object, bundled with JMH).
     * 
     * 使用明確的 Blackhole 對(duì)象,并將值下沉到那里。
     * (背景:Blackhole 只是另一個(gè) @State 對(duì)象,與 JMH 捆綁在一起)。
     */

    @Benchmark
    public void measureRight_2(Blackhole bh) {
        bh.consume(compute(x1));
        bh.consume(compute(x2));
    }

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

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

?
【附上官方Blackhole樣例(JMHSample_09_Blackholes) - 代碼運(yùn)行結(jié)果】

Benchmark                              Mode  Cnt   Score   Error  Units
JmhTestApp14_Balckhole.baseline        avgt    5  13.691 ± 0.661  ns/op
JmhTestApp14_Balckhole.measureRight_1  avgt    5  22.318 ± 1.664  ns/op
JmhTestApp14_Balckhole.measureRight_2  avgt    5  26.079 ± 3.079  ns/op
JmhTestApp14_Balckhole.measureWrong    avgt    5  13.276 ± 1.931  ns/op

?
【官方Dead Code樣例(JMHSample_09_Blackholes)注解 - 谷歌和百度翻譯互補(bǔ)】

??見代碼中的注釋

?

4、避免常量折疊(Constant Folding)

??常量折疊是Java編譯器早期的一種優(yōu)化——編譯優(yōu)化。在javac對(duì)源文件進(jìn)行編譯的過程中,通過詞法分析可以發(fā)現(xiàn)某些常量是可以被折疊的,也就是可以直接將計(jì)算結(jié)果存放到聲明中,而不需要在執(zhí)行階段再次進(jìn)行運(yùn)算。比如:

private final int x = 10;
private final int y = x*20;

??在編譯階段,y的值將被直接賦予200,這就是所謂的常量折疊。
?
【Constant Folding樣例 - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(避免Constant Folding,即常量折疊)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding {

    /**
     * x1和x2是使用final修飾的常量
     */
    private final double x1 = 124.456;
    private final double x2 = 342.456;

    /**
     * y1則是普通的成員變量
     */
    private double y1 = 124.456;
    /**
     * y2則是普通的成員變量
     */
    private double y2 = 342.456;

    /**
     * 直接返回124.456×342.456的計(jì)算結(jié)果,主要用它來作基準(zhǔn)
     * @return
     */
    @Benchmark
    public double returnDirect()
    {
        return 42_620.703936d;
    }

    /**
     * 兩個(gè)常量相乘,我們需要驗(yàn)證在編譯器的早期優(yōu)化階段是否直接計(jì)算出了x1乘以x2的值
     * @return
     */
    @Benchmark
    public double returnCalculate_1()
    {
        return x1 * x2;
    }

    /**
     * 較為復(fù)雜的計(jì)算,計(jì)算兩個(gè)未被final修飾的變量,主要也是用它來作為對(duì)比的基準(zhǔn)
     * @return
     */
    @Benchmark
    public double returnCalculate_2()
    {
        return Math.log(y1) * Math.log(y2);
    }

    /**
     * 較為復(fù)雜的計(jì)算,操作的同樣是final修飾的常量,查看是否在編譯器優(yōu)化階段進(jìn)行了常量的折疊行為
     * @return
     */
    @Benchmark
    public double returnCalculate_3()
    {
        return Math.log(x1) * Math.log(x2);
    }

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

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

?
【Constant Folding樣例 - 代碼運(yùn)行結(jié)果】

Benchmark                                                                      Mode  Cnt   Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_1  avgt    5   1.873 ± 0.119  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_2  avgt    5  36.126 ± 2.372  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_3  avgt    5   1.888 ± 0.169  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnDirect       avgt    5   1.869 ± 0.115  ns/op

??我們可以看到,1、3、4三個(gè)方法的統(tǒng)計(jì)數(shù)據(jù)幾乎相差無幾,這也就意味著在編譯器優(yōu)化的時(shí)候發(fā)生了常量折疊,這些方法在運(yùn)行階段根本不需要再進(jìn)行計(jì)算,直接將結(jié)果返回即可,而第二個(gè)方法的統(tǒng)計(jì)數(shù)據(jù)就沒那么好看了,因?yàn)樵缙诘木幾g階段不會(huì)對(duì)其進(jìn)行任何的優(yōu)化。
?
【附上官方Constant Folding樣例(JMHSample_10_ConstantFold) - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:官方Constant Folding樣例
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_ConstantFolding {

    /**
     * The flip side of dead-code elimination is constant-folding.
     *
     * 死碼消除的另一面是常量折疊。
     *
     * If JVM realizes the result of the computation is the same no matter what,
     * it can cleverly optimize it. In our case, that means we can move the
     * computation outside of the internal JMH loop.
     *
     * 如果 JVM 意識(shí)到無論如何計(jì)算的結(jié)果都是一樣的,它可以巧妙地優(yōu)化它。
     * 在我們的例子中,這意味著我們可以將計(jì)算移到內(nèi)部 JMH 循環(huán)之外。
     *
     * This can be prevented by always reading the inputs from non-final
     * instance fields of @State objects, computing the result based on those
     * values, and follow the rules to prevent DCE.
     *
     * 這可以通過始終讀取 @State 對(duì)象的非最終實(shí)例字段的輸入,根據(jù)這些值計(jì)算結(jié)果,并遵循防止 DCE 的規(guī)則來防止。
     */

    // IDEs will say "Oh, you can convert this field to local variable". Don't. Trust. Them.
    // IDEs 會(huì)說“哦,你可以將這個(gè)字段轉(zhuǎn)換為局部變量”。不要.相信.它們.
    // (While this is normally fine advice, it does not work in the context of measuring correctly.)
    // (雖然這通常是很好的建議,但它在正確測量的情況下不起作用。)
    private double x = Math.PI;

    // IDEs will probably also say "Look, it could be final". Don't. Trust. Them. Either.
    // IDEs 可能還會(huì)說“看,它可能是最終版本”。 也.不要.相信.它們.
    // (While this is normally fine advice, it does not work in the context of measuring correctly.)
    // (雖然這通常是很好的建議,但它在正確測量的情況下不起作用。)
    private final double wrongX = Math.PI;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    @Benchmark
    public double baseline() {
        // simply return the value, this is a baseline
        // 簡單地返回值,這是一個(gè)基線
        return Math.PI;
    }

    @Benchmark
    public double measureWrong_1() {
        // This is wrong: the source is predictable, and computation is foldable.
        // 這是錯(cuò)誤的:來源是可預(yù)測的,計(jì)算是可折疊的。
        return compute(Math.PI);
    }

    @Benchmark
    public double measureWrong_2() {
        // This is wrong: the source is predictable, and computation is foldable.
        // 這是錯(cuò)誤的:來源是可預(yù)測的,計(jì)算是可折疊的。
        return compute(wrongX);
    }

    @Benchmark
    public double measureRight() {
        // This is correct: the source is not predictable.
        // 這是正確的:來源是不可預(yù)測的。
        return compute(x);
    }

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

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

?
【附上官方Constant Folding樣例(JMHSample_10_ConstantFold) - 代碼運(yùn)行結(jié)果】

Benchmark                                    Mode  Cnt   Score   Error  Units
JmhTestApp14_ConstantFolding.baseline        avgt    5   1.871 ± 0.077  ns/op
JmhTestApp14_ConstantFolding.measureRight    avgt    5  13.989 ± 0.909  ns/op
JmhTestApp14_ConstantFolding.measureWrong_1  avgt    5   1.846 ± 0.075  ns/op
JmhTestApp14_ConstantFolding.measureWrong_2  avgt    5   1.870 ± 0.090  ns/op

?
【官方Constant Folding樣例(JMHSample_10_ConstantFold)注解 - 谷歌和百度翻譯互補(bǔ)】

??見代碼中的注釋

?

5、避免循環(huán)展開(Loop Unwinding)

??我們?cè)诰帉慗MH代碼的時(shí)候,除了要避免Dead Code以及減少對(duì)常量的引用之外,還要盡可能地避免或者減少在基準(zhǔn)測試方法中出現(xiàn)循環(huán),因?yàn)檠h(huán)代碼在運(yùn)行階段(JVM后期優(yōu)化)極有可能被“痛下殺手”進(jìn)行相關(guān)的優(yōu)化,這種優(yōu)化被稱為循環(huán)展開,下面我們來看一下什么是循環(huán)展開(Loop Unwinding)。

int sum=0;
for(int i = 0;i<100;i++){
    sum+=i;
}

??上面的例子中,sum=sum+i 這樣的代碼會(huì)被執(zhí)行100次,也就是說,JVM會(huì)向CPU發(fā)送100次這樣的計(jì)算指令,這看起來并沒有什么,但是JVM的設(shè)計(jì)者們會(huì)認(rèn)為這樣的方式可以被優(yōu)化成如下形式(可能)。

int sum=0;
for(int i = 0;i<20; i+=5){
    sum+=i;
    sum+=i+1;
    sum+=i+2;
    sum+=i+3;
    sum+=i+4;
}

??優(yōu)化后將循環(huán)體中的計(jì)算指令批量發(fā)送給CPU,這種批量的方式可以提高計(jì)算的效率,假設(shè)1+2這樣的運(yùn)算執(zhí)行一次需要1納秒的CPU時(shí)間,那么在一個(gè)10次循環(huán)的計(jì)算中,我們覺得它可能是10納秒的CPU時(shí)間,但是真實(shí)的計(jì)算情況可能不足10納秒甚至更低。
?
【Loop Unwinding樣例 - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(避免Loop Unwinding,即循環(huán)展開)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding {

    private int x = 1;
    private int y = 2;

    @Benchmark
    public int measure()
    {
        return (x + y);
    }

    private int loopCompute(int times)
    {
        int result = 0;
        for (int i = 0; i < times; i++)
        {
            result += (x + y);
        }
        return result;
    }

    @OperationsPerInvocation
    @Benchmark
    public int measureLoop_1()
    {
        return loopCompute(1);
    }

    @OperationsPerInvocation(10)
    @Benchmark
    public int measureLoop_10()
    {
        return loopCompute(10);
    }

    @OperationsPerInvocation(100)
    @Benchmark
    public int measureLoop_100()
    {
        return loopCompute(100);
    }

    @OperationsPerInvocation(1000)
    @Benchmark
    public int measureLoop_1000()
    {
        return loopCompute(1000);
    }

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

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

?
【Loop Unwinding樣例 - 代碼運(yùn)行結(jié)果】

Benchmark                                                                   Mode  Cnt  Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measure           avgt    5  2.038 ± 0.167  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_1     avgt    5  2.112 ± 0.548  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_10    avgt    5  0.226 ± 0.013  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_100   avgt    5  0.026 ± 0.003  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_1000  avgt    5  0.023 ± 0.002  ns/op

??上面的代碼中,measure() 方法進(jìn)行了 x+y 的計(jì)算,measureLoop_1() 方法與 measure() 方法幾乎是等價(jià)的,也是進(jìn)行了 x+y 的計(jì)算,但是 measureLoop_10() 方法對(duì) result+=(x+y) 進(jìn)行了10次這樣的操作,其實(shí)說白了就是調(diào)用了10次 measure() 或者 loopCompute(times=1) 。但是我們肯定不能直接拿10次的運(yùn)算和1次運(yùn)算所耗費(fèi)的CPU時(shí)間去做比較,因此 @OperationsPerInvocation(10) 注解的作用就是在每一次 對(duì)measureLoop_10() 方法進(jìn)行基準(zhǔn)調(diào)用的時(shí)候?qū)p操作記為10次。
?
??通過JMH的基準(zhǔn)測試我們不難發(fā)現(xiàn),在循環(huán)次數(shù)多的情況下,折疊的情況也比較多,因此性能會(huì)比較好,說明JVM在運(yùn)行期對(duì)我們的代碼進(jìn)行了優(yōu)化。
?
【附上官方Loop Unwinding樣例(JMHSample_11_Loops) - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:官方Loop Unwinding樣例
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_LoopUnwinding {

    /**
     * It would be tempting for users to do loops within the benchmarked method.
     * (This is the bad thing Caliper taught everyone). These tests explain why
     * this is a bad idea.
     *
     * 對(duì)于用戶來說,在基準(zhǔn)方法中進(jìn)行循環(huán)是很有吸引力的。
     * (這是 Caliper 教給大家的壞事)。這些測試解釋了為什么這是一個(gè)壞主意。
     *
     * Looping is done in the hope of minimizing the overhead of calling the
     * test method, by doing the operations inside the loop instead of inside
     * the method call. Don't buy this argument; you will see there is more
     * magic happening when we allow optimizers to merge the loop iterations.
     *
     * 循環(huán)是為了最小化調(diào)用測試方法的開銷,通過在循環(huán)內(nèi)而不是在方法調(diào)用內(nèi)進(jìn)行操作。
     * 不要相信這個(gè)論點(diǎn); 當(dāng)我們?cè)试S優(yōu)化器合并循環(huán)迭代時(shí),您會(huì)看到更多神奇的事情發(fā)生。
     */

    /**
     * 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.
     * 這是您使用JMH所做的。
     */

    @Benchmark
    public int measureRight() {
        return (x + y);
    }

    /**
     * The following tests emulate the naive looping.
     * 以下測試模擬了天真的循環(huán)。
     * This is the Caliper-style benchmark.
     * 這是 Caliper 風(fēng)格的基準(zhǔn)測試。
     */
    private int reps(int reps) {
        int s = 0;
        for (int i = 0; i < reps; i++) {
            s += (x + y);
        }
        return s;
    }

    /**
     * We would like to measure this with different repetitions count.
     * 我們想用不同的重復(fù)次數(shù)來衡量這一點(diǎn)。
     * Special annotation is used to get the individual operation cost.
     * 使用特殊注釋來獲得單個(gè)操作成本。
     */

    @Benchmark
    @OperationsPerInvocation(1)
    public int measureWrong_1() {
        return reps(1);
    }

    @Benchmark
    @OperationsPerInvocation(10)
    public int measureWrong_10() {
        return reps(10);
    }

    @Benchmark
    @OperationsPerInvocation(100)
    public int measureWrong_100() {
        return reps(100);
    }

    @Benchmark
    @OperationsPerInvocation(1_000)
    public int measureWrong_1000() {
        return reps(1_000);
    }

    @Benchmark
    @OperationsPerInvocation(10_000)
    public int measureWrong_10000() {
        return reps(10_000);
    }

    @Benchmark
    @OperationsPerInvocation(100_000)
    public int measureWrong_100000() {
        return reps(100_000);
    }

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

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

?
【附上官方Loop Unwinding樣例(JMHSample_11_Loops) - 代碼運(yùn)行結(jié)果】

Benchmark                                       Mode  Cnt  Score   Error  Units
JmhTestApp14_LoopUnwinding.measureRight         avgt    5  2.326 ± 0.089  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_1       avgt    5  2.052 ± 0.085  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_10      avgt    5  0.225 ± 0.006  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_100     avgt    5  0.026 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_1000    avgt    5  0.022 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_10000   avgt    5  0.019 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_100000  avgt    5  0.017 ± 0.002  ns/op

?
【官方Loop Unwinding樣例(JMHSample_11_Loops)注解 - 谷歌和百度翻譯互補(bǔ)】

??見代碼中的注釋

?

6、Fork用于避免 配置文件引導(dǎo)的優(yōu)化(profile-guided optimizations)

??Fork是用來干什么的呢?本節(jié)將會(huì)為大家介紹Fork的作用以及JVM的配置文件引導(dǎo)的優(yōu)化(profile-guided optimizations)。
?
??在開始解釋Fork之前,我們想象一下平時(shí)是如何進(jìn)行應(yīng)用性能測試的,比如我們要測試一下Redis分別在50、100、200個(gè)線程中同時(shí)進(jìn)行共計(jì)一億次的寫操作時(shí)的響應(yīng)速度,一般會(huì)怎樣做?首先,我們會(huì)將Redis庫清空,盡可能地保證每一次測試的時(shí)候,不同的測試用例站在同樣的起跑線上,比如,服務(wù)器內(nèi)存的大小、服務(wù)器磁盤的大小、服務(wù)器CPU的大小等基本上相同,這樣的對(duì)比才是有意義的,然后根據(jù)測試用例對(duì)其進(jìn)行測試,接著清理Redis服務(wù)器資源,使其回到測試之前的狀態(tài),最后統(tǒng)計(jì)測試結(jié)果做出測試報(bào)告。
?
??Fork的引入也是考慮到了這個(gè)問題,雖然Java支持多線程,但是不支持多進(jìn)程,這就導(dǎo)致了所有的代碼都在一個(gè)進(jìn)程中運(yùn)行,相同的代碼在不同時(shí)刻的執(zhí)行可能會(huì)引入前一階段對(duì)進(jìn)程profiler的優(yōu)化,甚至?xí)烊肫渌aprofiler優(yōu)化時(shí)的參數(shù),這很有可能會(huì)導(dǎo)致我們所編寫的微基準(zhǔn)測試出現(xiàn)不準(zhǔn)確的問題。對(duì)于這種說法大家可能會(huì)覺得有些抽象,通過例子去理解更好。
?
【Fork設(shè)置為0樣例 - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(Fork用于避免 profile-guided optimizations)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
// 將Fork設(shè)置為0
@Fork(0)
// 將Fork設(shè)置為1
//@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Fork {

    // Inc1 和Inc2的實(shí)現(xiàn)完全一樣
    interface Inc
    {
        int inc();
    }

    public static class Inc1 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    public static class Inc2 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    private Inc inc1 = new Inc1();
    private Inc inc2 = new Inc2();

    private int measure(Inc inc)
    {
        int result = 0;
        for (int i = 0; i < 10; i++)
        {
            result += inc.inc();
        }
        return result;
    }

    @Benchmark
    public int measure_inc_1()
    {
        return this.measure(inc1);
    }

    @Benchmark
    public int measure_inc_2()
    {
        return this.measure(inc2);
    }

    @Benchmark
    public int measure_inc_3()
    {
        return this.measure(inc1);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.class.getSimpleName())
                .build();

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

?
【Fork設(shè)置為0樣例 - 代碼運(yùn)行結(jié)果】

Benchmark                                                      Mode  Cnt  Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_1  avgt    5  0.002 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_2  avgt    5  0.012 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_3  avgt    5  0.012 ±  0.001  us/op

??將Fork設(shè)置為0,每一個(gè)基準(zhǔn)測試方法都將會(huì)與 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 使用同一個(gè)JVM進(jìn)程,因此基準(zhǔn)測試方法可能會(huì)混入 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 進(jìn)程的Profiler。
?
??measure_inc_1和 measure_inc_2 的實(shí)現(xiàn)方式幾乎是一致的,它們的性能卻存在著較大的差距,雖然 measure_inc_1 和 measure_inc_3 的代碼實(shí)現(xiàn)完全相同,但還是存在著不同的性能數(shù)據(jù),這其實(shí)就是JVM的 profiler-guided optimizations 導(dǎo)致的,由于我們所有的基準(zhǔn)測試方法都與 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 的JVM進(jìn)程共享,因此難免在其中混入 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 進(jìn)程的Profiler,但是在將Fork設(shè)置為1的時(shí)候,也就是說每一次運(yùn)行基準(zhǔn)測試時(shí)都會(huì)開辟一個(gè)全新的JVM進(jìn)程對(duì)其進(jìn)行測試,那么多個(gè)基準(zhǔn)測試之間將不會(huì)再存在干擾。
?
【Fork設(shè)置為1樣例 - 代碼】

package cn.zhuangyt.javabase.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;

/**
 * JMH測試14:編寫正確的微基準(zhǔn)測試用例(Fork用于避免 profile-guided optimizations)
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
// 將Fork設(shè)置為0
//@Fork(0)
// 將Fork設(shè)置為1
@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Fork {

    // Inc1 和Inc2的實(shí)現(xiàn)完全一樣
    interface Inc
    {
        int inc();
    }

    public static class Inc1 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    public static class Inc2 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    private Inc inc1 = new Inc1();
    private Inc inc2 = new Inc2();

    private int measure(Inc inc)
    {
        int result = 0;
        for (int i = 0; i < 10; i++)
        {
            result += inc.inc();
        }
        return result;
    }

    @Benchmark
    public int measure_inc_1()
    {
        return this.measure(inc1);
    }

    @Benchmark
    public int measure_inc_2()
    {
        return this.measure(inc2);
    }

    @Benchmark
    public int measure_inc_3()
    {
        return this.measure(inc1);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.class.getSimpleName())
                .build();

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

?
【Fork設(shè)置為1樣例 - 代碼運(yùn)行結(jié)果】

Benchmark                                                      Mode  Cnt  Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_1  avgt    5  0.003 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_2  avgt    5  0.003 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_3  avgt    5  0.003 ±  0.001  us/op

??以上輸出是將Fork設(shè)置為1的結(jié)果,是不是合理了很多,若將Fork設(shè)置為0,則會(huì)與運(yùn)行基準(zhǔn)測試的類共享同樣的進(jìn)程Profiler,若設(shè)置為1則會(huì)為每一個(gè)基準(zhǔn)測試方法開辟新的進(jìn)程去運(yùn)行,當(dāng)然,你可以將Fork設(shè)置為大于1的數(shù)值,那么它將多次運(yùn)行在不同的進(jìn)程中,不過一般情況下,我們只需要將Fork設(shè)置為1即可。
?
【附上官方Fork樣例(JMHSample_12_Forking) - 代碼】

package cn.zhuangyt.javabase.jmh;

import cn.zhuangyt.javabase.jmh.jmh_sample.JMHSample_12_Forking;
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;

/**
 * JMH測試14:官方Forks樣例
 * @author 大白有點(diǎn)菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Fork {

    /**
     * JVMs are notoriously good at profile-guided optimizations. This is bad
     * for benchmarks, because different tests can mix their profiles together,
     * and then render the "uniformly bad" code for every test. Forking (running
     * in a separate process) each test can help to evade this issue.
     *
     * JVM 以擅長配置文件引導(dǎo)的優(yōu)化而著稱。 這對(duì)基準(zhǔn)測試不利,因?yàn)椴煌臏y試可以將它們的配置文件混合在一起,
     * 然后為每個(gè)測試呈現(xiàn)“一致糟糕”的代碼。 分叉(在單獨(dú)的進(jìn)程中運(yùn)行)每個(gè)測試可以幫助避免這個(gè)問題。
     *
     * JMH will fork the tests by default.
     *
     * JMH 將默認(rèn)分叉測試。
     */

    /**
     * Suppose we have this simple counter interface, and two implementations.
     * Even though those are semantically the same, from the JVM standpoint,
     * those are distinct classes.
     *
     * 假設(shè)我們有這個(gè)簡單的計(jì)數(shù)器接口和兩個(gè)實(shí)現(xiàn)。盡管它們?cè)谡Z義上是相同的,但從 JVM 的角度來看,它們是不同的類。
     */

    public interface Counter {
        int inc();
    }

    public static class Counter1 implements JMHSample_12_Forking.Counter {
        private int x;

        @Override
        public int inc() {
            return x++;
        }
    }

    public static class Counter2 implements JMHSample_12_Forking.Counter {
        private int x;

        @Override
        public int inc() {
            return x++;
        }
    }

    /**
     * And this is how we measure it.
     * 這就是我們衡量它的方式。
     * Note this is susceptible for same issue with loops we mention in previous examples.
     * 請(qǐng)注意,這很容易受到我們?cè)谇懊媸纠刑岬降难h(huán)的相同問題的影響。
     */

    public int measure(JMHSample_12_Forking.Counter c) {
        int s = 0;
        for (int i = 0; i < 10; i++) {
            s += c.inc();
        }
        return s;
    }

    /**
     * These are two counters.
     */
    JMHSample_12_Forking.Counter c1 = new JMHSample_12_Forking.Counter1();
    JMHSample_12_Forking.Counter c2 = new JMHSample_12_Forking.Counter2();

    /**
     * We first measure the Counter1 alone...
     * 我們首先單獨(dú)測量 Counter1 ...
     * Fork(0) helps to run in the same JVM.
     * Fork(0) 有助于在同一個(gè) JVM 中運(yùn)行。
     */

    @Benchmark
    @Fork(0)
    public int measure_1_c1() {
        return measure(c1);
    }

    /**
     * Then Counter2...
     * 然后到 Counter2...
     */

    @Benchmark
    @Fork(0)
    public int measure_2_c2() {
        return measure(c2);
    }

    /**
     * Then Counter1 again...
     * 然后再次到 Counter1
     */

    @Benchmark
    @Fork(0)
    public int measure_3_c1_again() {
        return measure(c1);
    }

    /**
     * These two tests have explicit @Fork annotation.
     * JMH takes this annotation as the request to run the test in the forked JVM.
     * It's even simpler to force this behavior for all the tests via the command
     * line option "-f". The forking is default, but we still use the annotation
     * for the consistency.
     *
     * 這兩個(gè)測試有顯示的 @Fork 注釋。
     * JMH 將此注釋作為在分叉的 JVM 中運(yùn)行測試的請(qǐng)求。
     * 通過命令行選項(xiàng)“-f”為所有測試強(qiáng)制執(zhí)行此行為甚至更簡單。 分叉是默認(rèn)的,但我們?nèi)匀皇褂米⑨寔肀3忠恢滦浴?     *
     * This is the test for Counter1.
     * 這是 Counter1 的測試
     */

    @Benchmark
    @Fork(1)
    public int measure_4_forked_c1() {
        return measure(c1);
    }

    /**
     * ...and this is the test for Counter2.
     * 還有這是 Counter2 的測試
     */

    @Benchmark
    @Fork(1)
    public int measure_5_forked_c2() {
        return measure(c2);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Fork.class.getSimpleName())
                .build();

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

?
【附上官方Fork樣例(JMHSample_12_Forking) - 代碼運(yùn)行結(jié)果】

Benchmark                              Mode  Cnt   Score   Error  Units
JmhTestApp14_Fork.measure_1_c1         avgt    5   2.162 ± 0.129  ns/op
JmhTestApp14_Fork.measure_2_c2         avgt    5  12.490 ± 0.304  ns/op
JmhTestApp14_Fork.measure_3_c1_again   avgt    5  12.182 ± 0.605  ns/op
JmhTestApp14_Fork.measure_4_forked_c1  avgt    5   3.138 ± 0.162  ns/op
JmhTestApp14_Fork.measure_5_forked_c2  avgt    5   3.179 ± 0.302  ns/op

?
【官方Fork樣例注解(JMHSample_12_Forking) - 谷歌和百度翻譯互補(bǔ)】

??見代碼中的注釋

?

到了這里,關(guān)于性能調(diào)優(yōu)之JMH必知必會(huì)3:編寫正確的微基準(zhǔn)測試用例的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 聊聊Flink必知必會(huì)(五)

    聊聊Flink的必知必會(huì)(三) 聊聊Flink必知必會(huì)(四) 從源碼中,根據(jù)關(guān)鍵的代碼,梳理一下Flink中的時(shí)間與窗口實(shí)現(xiàn)邏輯。 對(duì)數(shù)據(jù)流執(zhí)行 keyBy() 操作后,再調(diào)用 window() 方法,就會(huì)返回 WindowedStream ,表示分區(qū)后又加窗的數(shù)據(jù)流。如果數(shù)據(jù)流沒有經(jīng)過分區(qū),直接調(diào)用 window() 方法則會(huì)返

    2024年02月05日
    瀏覽(21)
  • MySQL必知必會(huì)(初級(jí)篇)

    MySQL必知必會(huì)(初級(jí)篇)

    數(shù)據(jù)庫 (DataBase,DB),是統(tǒng)一管理的、長期存儲(chǔ)在計(jì)算機(jī)內(nèi)的、有組織的相關(guān)數(shù)據(jù)的集合。特點(diǎn)是數(shù)據(jù)見聯(lián)系密切、冗余度小、獨(dú)立性高、易擴(kuò)展,并且可以為各類用戶共享。 MySQL :是一個(gè)關(guān)系型數(shù)據(jù)庫管理系統(tǒng),由瑞典MySQL AB 公司開發(fā),屬于 Oracle 旗下產(chǎn)品。MySQL 是最流行的

    2023年04月08日
    瀏覽(19)
  • 《SQL 必知必會(huì)》全解析

    不要哀求,學(xué)會(huì)爭取。若是如此,終有所獲。 原文:https://mp.weixin.qq.com/s/zbOqyAtsWsocarsFIGdGgw 你是否還在煩惱 SQL 該從何學(xué)起,或者學(xué)了 SQL 想找個(gè)地方練練手?好巧不巧,最近在工作之余登上??停l(fā)現(xiàn)了??筒恢郎稌r(shí)候上線了SQL 必知必會(huì)的練習(xí)題。 《SQL 必知必會(huì)》作為麻

    2024年02月08日
    瀏覽(27)
  • 聊聊Flink必知必會(huì)(七)

    雖然數(shù)據(jù)流中的許多操作一次只查看一個(gè)單獨(dú)的事件(例如事件解析器),但某些操作會(huì)記住多個(gè)事件的信息(例如窗口算子)。 這些操作稱為有狀態(tài)的(stateful)。 有狀態(tài)操作的一些示例: 當(dāng)應(yīng)用程序搜索某些事件模式(event patterns)時(shí),狀態(tài)(state)將存儲(chǔ)迄今為止遇到的事件序

    2024年02月04日
    瀏覽(21)
  • 聊聊Flink必知必會(huì)(六)

    Flink是一個(gè)分布式系統(tǒng),需要有效地分配和管理計(jì)算資源才能執(zhí)行流應(yīng)用程序。它集成了所有常見的集群資源管理器,如Hadoop YARN和Kubernetes,但也可以設(shè)置為作為一個(gè)獨(dú)立的集群運(yùn)行,甚至作為一個(gè)庫。 Flink運(yùn)行時(shí)由兩種類型的進(jìn)程組成:一個(gè)JobManager和一個(gè)或多個(gè)taskmanager。

    2024年02月04日
    瀏覽(64)
  • ChatGPT入門必知必會(huì)

    ChatGPT入門必知必會(huì)

    更多文章歡迎關(guān)注公眾號(hào): stackoveriow 2023年是真正意義上的AI之年,因?yàn)镃hatGPT 2007年,iPhone開啟了智能手機(jī)時(shí)代, 2023年,我們迎來了人工智能時(shí)代,我們正處于歷史的大轉(zhuǎn)折點(diǎn)上,這也許是啟蒙運(yùn)動(dòng)級(jí)別的思想和社會(huì)轉(zhuǎn)折,工業(yè)革命級(jí)別的生產(chǎn)和生活轉(zhuǎn)折 。繼22年12月份從GP

    2023年04月18日
    瀏覽(31)
  • 【數(shù)據(jù)庫】索引必知必會(huì)

    【數(shù)據(jù)庫】索引必知必會(huì)

    數(shù)據(jù)庫中索引(Index)是一種幫助快速查找數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),可以把它理解為書的目錄,通過索引能夠快速找到數(shù)據(jù)所在位置。 使用索引可以加快數(shù)據(jù)查找的效率,這是創(chuàng)建索引的最主要原因。 場景的索引數(shù)據(jù)結(jié)構(gòu)有:Hash表(通過hash算法快速定位數(shù)據(jù),但不適合范圍查詢,

    2023年04月20日
    瀏覽(26)
  • 抽象語法樹AST必知必會(huì)

    抽象語法樹AST必知必會(huì)

    打開前端項(xiàng)目中的 package.json,會(huì)發(fā)現(xiàn)眾多工具已經(jīng)占據(jù)了我們開發(fā)日常的各個(gè)角落,例如 JavaScript 轉(zhuǎn)譯、CSS 預(yù)處理、代碼壓縮、ESLint、Prettier 等。這些工具模塊大都不會(huì)交付到生產(chǎn)環(huán)境中,但它們的存在于我們的開發(fā)而言是不可或缺的。 有沒有想過這些工具的功能是如何實(shí)

    2024年02月16日
    瀏覽(26)
  • 必知必會(huì)Java命令-jps

    必知必會(huì)Java命令-jps

    你好,我是阿光。 最近想著把工作中使用過的java命令都梳理一下,方便日后查閱。雖然這類文章很多,但自己梳理總結(jié)后,還是會(huì)有一些新的收獲。這也是這篇筆記的由來。 今天先聊聊 jps 命令。 jps 命令是JDK提供的一個(gè)工具,用于查看目標(biāo)系統(tǒng)上的Java進(jìn)程基本信息(進(jìn)程

    2024年02月05日
    瀏覽(20)
  • 《MySQL 必知必會(huì)》課程筆記(三)

    《MySQL 必知必會(huì)》課程筆記(三)

    創(chuàng)建和修改數(shù)據(jù)表,是數(shù)據(jù)存儲(chǔ)過程中的重要?環(huán)。 我們不僅需要把表創(chuàng)建出來,還需要正確地設(shè)置限定條件,這樣才能確保數(shù)據(jù)的一致性和完整性。 同時(shí),表中的數(shù)據(jù)會(huì)隨著業(yè)務(wù)需求的變化而變化,添加和修改相應(yīng)的字段也是常見的操作。 首先,我們要知道 MySQL 創(chuàng)建表的

    2024年02月03日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包