?
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-core
和 jmh-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 5 ≈ 10?? us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog1 avgt 5 ≈ 10?? us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog2 avgt 5 ≈ 10?? 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ǔ)】
??見代碼中的注釋文章來源:http://www.zghlxwxcb.cn/news/detail-403731.html
?
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)!