日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

JMH簡(jiǎn)介

 nikybook 2016-02-16
本文由 ImportNew - hejiani 翻譯自 java-performance。歡迎加入翻譯小組。轉(zhuǎn)載請(qǐng)見(jiàn)文末要求。

JMH是新的microbenchmark(微基準(zhǔn)測(cè)試)框架(2013年首次發(fā)布)。與其他眾多框架相比它的特色優(yōu)勢(shì)在于,它是由Oracle實(shí)現(xiàn)JIT的相同人員開(kāi)發(fā)的。特別是我想提一下Aleksey Shipilev和他優(yōu)秀的博客文章。JMH可能與最新的Oracle JRE同步,其結(jié)果可信度很高。

JMH的示例鏈接。

使用JMH僅需滿足2個(gè)必要條件(其他所有都是建議選項(xiàng)):

  • 設(shè)置jmh-core的maven依賴
  • 使用@GenerateMicroBenchmark注解測(cè)試方法

本文將主要介紹JMH的基本規(guī)則和功能。第二篇文章將介紹JMH分析器。

如何運(yùn)行

在pom文件中加入依賴(在Maven Central查看jmh-core的最新版本):

1
2
3
4
5
6
7
<dependencies>
    <dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>0.4.2</version>
    </dependency>
</dependencies>

生成一個(gè)包含main方法的Java類。main方法中加入以下代碼:

1
2
3
4
5
6
Options opt = new OptionsBuilder()
                .include(".*" + YourClass.class.getSimpleName() + ".*")
                .forks(1)
                .build();
  
new Runner(opt).run();

測(cè)試方法使用@GenerateMicroBenchmark注解。運(yùn)行該類。

測(cè)試模式

測(cè)試方法上@BenchmarkMode注解表示使用特定的測(cè)試模式:

名稱 描述
Mode.Throughput 計(jì)算一個(gè)時(shí)間單位內(nèi)操作數(shù)量
Mode.AverageTime 計(jì)算平均運(yùn)行時(shí)間
Mode.SampleTime 計(jì)算一個(gè)方法的運(yùn)行時(shí)間(包括百分位)
Mode.SingleShotTime 方法僅運(yùn)行一次(用于冷測(cè)試模式)。或者特定批量大小的迭代多次運(yùn)行(具體查看后面的“`@Measurement“`注解)——這種情況下JMH將計(jì)算批處理運(yùn)行時(shí)間(一次批處理所有調(diào)用的總時(shí)間)
這些模式的任意組合 可以指定這些模式的任意組合——該測(cè)試運(yùn)行多次(取決于請(qǐng)求模式的數(shù)量)
Mode.All 所有模式依次運(yùn)行

時(shí)間單位

使用@OutputTimeUnit指定時(shí)間單位,它需要一個(gè)標(biāo)準(zhǔn)Java類型java.util.concurrent.TimeUnit作為參數(shù)??墒侨绻谝粋€(gè)測(cè)試中指定了多種測(cè)試模式,給定的時(shí)間單位將用于所有的測(cè)試(比如,測(cè)試SampleTime適宜使用納秒,但是throughput使用更長(zhǎng)的時(shí)間單位測(cè)量更合適)。

測(cè)試參數(shù)狀態(tài)

測(cè)試方法可能接收參數(shù)。這需要提供單個(gè)的參數(shù)類,這個(gè)類遵循以下4條規(guī)則:

  • 有無(wú)參構(gòu)造函數(shù)(默認(rèn)構(gòu)造函數(shù))
  • 是公共類
  • 內(nèi)部類應(yīng)該是靜態(tài)的
  • 該類必須使用@State注解

@State注解定義了給定類實(shí)例的可用范圍。JMH可以在多線程同時(shí)運(yùn)行的環(huán)境測(cè)試,因此需要選擇正確的狀態(tài)。

名稱 描述
Scope.Thread 默認(rèn)狀態(tài)。實(shí)例將分配給運(yùn)行給定測(cè)試的每個(gè)線程。
Scope.Benchmark 運(yùn)行相同測(cè)試的所有線程將共享實(shí)例。可以用來(lái)測(cè)試狀態(tài)對(duì)象的多線程性能(或者僅標(biāo)記該范圍的基準(zhǔn))。
Scope.Group 實(shí)例分配給每個(gè)線程組(查看后面的線程組部分)

除了將單獨(dú)的類標(biāo)記@State,也可以將你自己的benchmark類使用@State標(biāo)記。上面所有的規(guī)則對(duì)這種情況也適用。

狀態(tài)設(shè)置和清理

與JUnit測(cè)試類似,使用@Setup@TearDown注解標(biāo)記狀態(tài)類的方法(這些方法在JMH文檔中稱為fixtures)。setup/teardown方法的數(shù)量是任意的。這些方法不會(huì)影響測(cè)試時(shí)間(但是Level.Invocation可能影響測(cè)量精度)。

@Setup/@TearDown注解使用Level參數(shù)來(lái)指定何時(shí)調(diào)用fixture:

名稱 描述
Level.Trial 默認(rèn)level。全部benchmark運(yùn)行(一組迭代)之前/之后
Level.Iteration 一次迭代之前/之后(一組調(diào)用)
Level.Invocation 每個(gè)方法調(diào)用之前/之后(不推薦使用,除非你清楚這樣做的目的)

冗余代碼

冗余代碼消除是microbenchmark中眾所周知的問(wèn)題。通常的解決方法是以某種方式使用計(jì)算結(jié)果。JMH本身不會(huì)實(shí)施對(duì)冗余代碼的消除。但是如果你想消除冗余代碼——要做到測(cè)試程序返回值不為void。永遠(yuǎn)返回你的計(jì)算結(jié)果。JMH將完成剩余的工作。

如果測(cè)試程序需要返回多個(gè)值,將所有這些返回值使用省時(shí)操作結(jié)合起來(lái)(省時(shí)是指相對(duì)于獲取到所有結(jié)果所做操作的開(kāi)銷),或者使用BlackHole作為方法參數(shù),將所有的結(jié)果放入其中(注意某些情況下BlockHole.consume可能比手動(dòng)將結(jié)果組合起來(lái)開(kāi)銷更大)。BlackHole是一個(gè)thread-scoped類:

1
2
3
4
5
6
@GenerateMicroBenchmark
public void testSomething( BlackHole bh )
{
    bh.consume( Math.sin( state_field ));
    bh.consume( Math.cos( state_field ));
}

常量處理

如果計(jì)算結(jié)果是可預(yù)見(jiàn)的并且不依賴于狀態(tài)對(duì)象,它可能被JIT優(yōu)化。因此,最好總是從狀態(tài)對(duì)象讀取測(cè)試的輸入并且返回計(jì)算的結(jié)果。這條規(guī)則大體上用于單個(gè)返回值的情形。使用BlackHole對(duì)象JVM更難優(yōu)化它(但不是不可能被優(yōu)化)。下面測(cè)試的所有方法都不會(huì)被優(yōu)化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private double x = Math.PI;
  
@GenerateMicroBenchmark
public void bhNotQuiteRight( BlackHole bh )
{
    bh.consume( Math.sin( Math.PI ));
    bh.consume( Math.cos( Math.PI ));
}
  
@GenerateMicroBenchmark
public void bhRight( BlackHole bh )
{
    bh.consume( Math.sin( x ));
    bh.consume( Math.cos( x ));
}

返回單個(gè)值的情形更加復(fù)雜。下面的測(cè)試不會(huì)被優(yōu)化,但是如果使用Math.log替換Math.sin,那么testWrong方法將被常量值替換。

1
2
3
4
5
6
7
8
9
10
11
12
13
private double x = Math.PI;
  
@GenerateMicroBenchmark
public double testWrong()
{
    return Math.sin( Math.PI );
}
  
@GenerateMicroBenchmark
public double testRight()
{
    return Math.sin( x );
}

因此,為使測(cè)試更可靠要嚴(yán)格遵守以下規(guī)則:永遠(yuǎn)從狀態(tài)對(duì)象讀取測(cè)試輸入并返回計(jì)算的結(jié)果

循環(huán)

不要在測(cè)試中使用循環(huán)。JIT非常聰明,在循環(huán)中經(jīng)常出現(xiàn)不可預(yù)料的處理。要測(cè)試真實(shí)的計(jì)算,讓JMH處理剩余的部分。

在非統(tǒng)一開(kāi)銷操作情況下(比如測(cè)試處理列表的時(shí)間,這個(gè)列表在每個(gè)測(cè)試后有所增加),你可能使用@BenchmarkMode(Mode.SingleShotTime)@Measurement(batchSize = N)。但是不允許你自己實(shí)現(xiàn)測(cè)試的循環(huán)。

分支

默認(rèn)JMH為每個(gè)試驗(yàn)(迭代集合)fork一個(gè)新的java進(jìn)程。這樣可以防止前面收集的“資料”——其他被加載類以及它們執(zhí)行的信息對(duì)當(dāng)前測(cè)試的影響。比如,實(shí)現(xiàn)了相同接口的兩個(gè)類,測(cè)試它們的性能,那么第一個(gè)實(shí)現(xiàn)(目標(biāo)測(cè)試類)可能比第二個(gè)快,因?yàn)镴IT發(fā)現(xiàn)第二個(gè)實(shí)現(xiàn)類后就把第一個(gè)實(shí)現(xiàn)的直接方法調(diào)用替換為接口方法調(diào)用。

因此,不要把forks設(shè)為0,除非你清楚這樣做的目的

極少數(shù)情況下需要指定JVM分支數(shù)量時(shí),使用@Fork對(duì)方法注解,就可以設(shè)置分支數(shù)量,預(yù)熱(warmup)迭代數(shù)量和JVM分支的其他參數(shù)。

可能通過(guò)JMH API調(diào)用來(lái)指定JVM分支參數(shù)也有優(yōu)勢(shì)——可以使用一些JVM -XX:參數(shù),通過(guò)JMH API訪問(wèn)不到它。這樣就可以根據(jù)你的代碼自動(dòng)選擇最佳的JVM設(shè)置(new Runner(opt).run()以簡(jiǎn)便的形式返回了所有的測(cè)試結(jié)果)。

編譯器提示

可以為JIT提供關(guān)于如何使用測(cè)試程序中任何方法的提示?!叭魏畏椒ā笔侵溉魏蔚姆椒ā粌H僅是@GenerateMicroBenchmark注解的方法。使用@CompilerControl模式(還有更多模式,但是我不確定它們的有用程度):

名稱 描述
CompilerControl.Mode.DONT_INLINE 該方法不能被內(nèi)嵌。用于測(cè)量方法調(diào)用開(kāi)銷和評(píng)估是否該增加JVM的inline閾值
CompilerControl.Mode.INLINE 要求編譯器內(nèi)嵌該方法。通常與“`Mode.DONT_INLINE“`聯(lián)合使用,檢查內(nèi)嵌的利弊。
CompilerControl.Mode.EXCLUDE 不編譯該方法——解釋它。在該JIT有多好的圣戰(zhàn)中作為有用的參數(shù):)

注解控制測(cè)試

通過(guò)注解指定JMH參數(shù)。這些注解用在類或者方法上。方法注解總是優(yōu)先于類的注解。

名稱 描述
@Fork 需要運(yùn)行的試驗(yàn)(迭代集合)數(shù)量。每個(gè)試驗(yàn)運(yùn)行在單獨(dú)的JVM進(jìn)程中。也可以指定(額外的)JVM參數(shù)。
@Measurement 提供真正的測(cè)試階段參數(shù)。指定迭代的次數(shù),每次迭代的運(yùn)行時(shí)間和每次迭代測(cè)試調(diào)用的數(shù)量(通常使用@BenchmarkMode(Mode.SingleShotTime)測(cè)試一組操作的開(kāi)銷——而不使用循環(huán))
@Warmup 與@Measurement相同,但是用于預(yù)熱階段
@Threads 該測(cè)試使用的線程數(shù)。默認(rèn)是Runtime.getRuntime().availableProcessors()

CPU消耗

有時(shí)測(cè)試消耗一定CPU周期。通過(guò)靜態(tài)的BlackHole.consumeCPU(tokens)方法來(lái)實(shí)現(xiàn)。Token是一些CPU指令。這樣編寫(xiě)方法代碼就可以達(dá)到運(yùn)行時(shí)間依賴于該參數(shù)的目的(不被任何JIT/CPU優(yōu)化)。

多參數(shù)的測(cè)試運(yùn)行

很多情況下測(cè)試代碼包含多個(gè)參數(shù)集合。幸運(yùn)的是,要測(cè)試不同參數(shù)集合時(shí)JMH不會(huì)要求寫(xiě)多個(gè)測(cè)試方法?;蛘邷?zhǔn)確來(lái)說(shuō),測(cè)試參數(shù)是基本類型,基本包裝類型或者String時(shí),JMH提供了解決方法。

程序需要完成:

  1. 定義@State對(duì)象
  2. 在其中定義所有的參數(shù)字段
  3. 每個(gè)字段都使用@Param注解

@Param注解使用String數(shù)組作為參數(shù)。這些字符串在任何@Setup方法被調(diào)用前轉(zhuǎn)換為字段類型。然而,JMH文檔中聲稱這些字段值在@Setup方法中不能被訪問(wèn)。

JMH使用所有@Param字段的輸出結(jié)果。因此,如果第一個(gè)字段有2個(gè)參數(shù),第二個(gè)字段有5個(gè)參數(shù),測(cè)試將運(yùn)行2 * 5 * Forks次。

線程組——非統(tǒng)一的多線程

我們已經(jīng)提到@State(Scope.Benchmark)用來(lái)測(cè)試多線程訪問(wèn)狀態(tài)對(duì)象的情形。并發(fā)程度通過(guò)用來(lái)測(cè)試的線程數(shù)量設(shè)置。

可能也需要定義對(duì)狀態(tài)對(duì)象非統(tǒng)一訪問(wèn)的情況——比如測(cè)試“讀取——寫(xiě)入”場(chǎng)景時(shí),讀線程數(shù)通常高于寫(xiě)線程數(shù)量。JMH使用線程組來(lái)應(yīng)對(duì)這種情形。

為設(shè)置測(cè)試組,需要:

  1. 使用@Group(name)注解標(biāo)記所有的測(cè)試方法,為同一個(gè)組中的所有測(cè)試設(shè)置相同的名稱(否則這些測(cè)試將獨(dú)立運(yùn)行——沒(méi)有任何警告提示!)
  2. 使用@GroupThreads(threadsNumber)注解標(biāo)記每個(gè)測(cè)試,指定運(yùn)行給定方法的線程數(shù)量。

JMH將啟動(dòng)給定組的所有@GroupThreads,并發(fā)運(yùn)行相同實(shí)驗(yàn)中同一組的所有測(cè)試。組和每個(gè)方法的結(jié)果將單獨(dú)給出。

多線程——偽共享字段訪問(wèn)

你可能知道這樣一個(gè)事實(shí),大多數(shù)現(xiàn)代x86 CPU有64字節(jié)的cache line(緩存行)。CPU緩存提高了數(shù)據(jù)讀取速率,但同時(shí),如果你需要從多個(gè)線程同時(shí)讀寫(xiě)兩個(gè)鄰近的字段,也會(huì)產(chǎn)生性能瓶頸。這種情況稱為“偽共享”——字段似乎是獨(dú)立訪問(wèn)的,但是實(shí)際上它們?cè)谟布用娴南嗷ジ?jìng)爭(zhēng)。

這個(gè)問(wèn)題通常的解決方案是兩邊都增加至少128字節(jié)的虛擬數(shù)據(jù)。因?yàn)镴VM可以將類的字段重排序,在相同的類內(nèi)部增加可能不能正確運(yùn)行。

更加健壯的方法是使用類層次——JVM通常將屬于同一個(gè)類的字段放在一起。比如,定義類A有一個(gè)只讀字段,類B繼承類A且定義16個(gè)long字段,類C繼承類B定義可寫(xiě)字段,最后類D繼承類C定義另一個(gè)16個(gè)long字段——這就防止了被分配在下一個(gè)內(nèi)存中對(duì)象的寫(xiě)變量競(jìng)爭(zhēng)。

以防讀寫(xiě)的字段類型相同,也可以使用兩個(gè)數(shù)據(jù)位置相互距離很遠(yuǎn)的稀疏數(shù)組。在前面的情況中不要使用數(shù)組——它們是對(duì)象特定類型,僅需要增加4或8字節(jié)(取決于JVM設(shè)置)。

這個(gè)問(wèn)題的另一種解決方法是如果你已經(jīng)用到了Java 8:在可寫(xiě)字段上使用@sun.misc.Contended以及-XX:-RestrictContended的JVM設(shè)置。更多細(xì)節(jié),參見(jiàn)Aleksey Shipilev的說(shuō)明。

JMH是如何解決競(jìng)爭(zhēng)字段訪問(wèn)的呢?它在兩邊都增加了@State對(duì)象,但是這并不能在單一對(duì)象內(nèi)部對(duì)個(gè)別的字段增加——需要自己來(lái)處理。

總結(jié)

  • JMH用于各種類型的microbenchmark——每個(gè)測(cè)試從納秒到毫秒。它關(guān)注所有可測(cè)量的邏輯,測(cè)試人員只需編寫(xiě)測(cè)試方法的任務(wù)代碼。JMH也包含對(duì)所有類型多線程測(cè)試的內(nèi)在支持——統(tǒng)一(所有線程運(yùn)行相同代碼)和非統(tǒng)一(線程分組,每個(gè)組運(yùn)行自己的代碼)。
  • 如果僅僅一條規(guī)則需要記住的話,那就是——永遠(yuǎn)從@State對(duì)象讀取測(cè)試輸入并返回計(jì)算的結(jié)果(無(wú)論結(jié)果是明確的還是通過(guò) BlackHole對(duì)象返回)。
原文鏈接: java-performance 翻譯: ImportNew.com - hejiani
譯文鏈接: http://www./12548.html
[ 轉(zhuǎn)載請(qǐng)保留原文出處、譯者和譯文鏈接。]

關(guān)于作者: hejiani

(新浪微博:@jianihe

查看hejiani的更多文章 >> 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多