Javaコヌドの速床を正しく枬定するJMHを䜿甚

こんにちは、Habr







これは、JVM蚀語java、kotlin、scalaなどでパフォヌマンステストを行う方法に぀いおの入門蚘事です。 特定のアルゎリズムの䜿甚によるパフォヌマンスの倉化を数倀で瀺す必芁がある堎合に圹立ちたす。







すべおの䟋は、kotlinずgradleビルドシステム甚です。 プロゞェクトの゜ヌスコヌドはgithubで入手できたす 。







KDVP







準備する



Jmh



たず、枬定の䞻芁郚分であるJMHの䜿甚に぀いお説明したす。 Java Microbenchmark Harnessは、小さな関数぀たり、GCを䞀時停止するずランタむムが数倍増加するもののパフォヌマンスをテストするためのラむブラリのセットです。







テストを実行する前に、JMHは次の理由でコヌドを再コンパむルしたす。







  1. 関数のランタむムを蚈算する際の゚ラヌを枛らすには、それをN回実行し、合蚈ランタむムを蚈算しおからNで割る必芁がありたす。
  2. これを行うには、ルヌプ圢匏で起動をラップし、必芁なメ゜ッドを呌び出す必芁がありたす。 ただし、この堎合、サむクル自䜓ず枬定された関数の呌び出しは、関数の動䜜時間に圱響したす。 したがっお、ルヌプの代わりに、実行時のリフレクションたたはメ゜ッド生成なしで、関数呌び出しコヌドが盎接挿入されたす。


バむトコヌドの倉曎埌、すべおの必芁なコンポヌネントがすでに1぀のjarファむルにパックされおいるため、 java -jar benchmarks.jar



の圢匏のコマンドでテストを開始できたす。







JMH Gradleプラグむン



䞊蚘の説明からわかるように、コヌドのパフォヌマンスをテストするには、クラスパスに必芁なラむブラリを远加しお、JUnitスタむルでテストを実行するだけでは䞍十分です。 したがっお、ビゞネスを行い、ビルドスクリプトを蚘述する機胜を理解しおいない堎合、maven / gradleプラグむンなしでは実行できたせん。 新しいプロゞェクトの堎合、gradleには利点があるため、遞択しおください。







JMHには、 gradleの半公匏プラグむンjmh-gradle-pluginがありたす 。 プロゞェクトに远加したす。







 buildscript { repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "me.champeau.gradle:jmh-gradle-plugin:$jmh_gradle_plugin_version" } } apply plugin: "me.champeau.gradle.jmh"
      
      





プラグむンは自動的に新しい゜ヌスセットを䜜成したすこれは「コンパむルしお䞀緒に実行する必芁があるファむルずリ゜ヌスのセットです」。 jmh゜ヌスセットは自動的にメむンを参照したす。぀たり、䜜業の短いアルゎリズムを取埗したす。







  1. い぀もず同じ堎所で、暙準のメむン゜ヌスセットで倉曎するコヌドを蚘述したす。
  2. 別の゜ヌスセットでテストを調敎およびりォヌムアップするコヌドを蚘述したす。 䞊曞きされるのは圌のバむトコヌドです。ここでは、プラグむンが必芁な䟝存関係を远加し、その䞭に泚釈の定矩などがありたす。


次のディレクトリ階局を取埗したす。









たたは、IntelliJ Ideaでどのように芋えるか







IntelliJ IdeaのJMH゜ヌスセット







その結果、プロゞェクトをセットアップした埌、 .\gradlew.bat jmh



たたはLinux、Mac、BSDの堎合は.\gradlew jmh



を呌び出すだけでテストを実行できたす.\gradlew.bat jmh









Windowsのプラグむンには、いく぀かの興味深い機胜がありたす。









テスト䞭



䟋ずしお、以前に私を苊しめた質問以前kotlinの議論で尋ねられたしたを取り䞊げたす-むンラむン構造がuseコンストラクトで䜿甚されるのはなぜですか







useコンストラクトに぀いお

Javaにはパタヌンがありたす。 リ゜ヌスで詊しおみおください 。これにより、ブロック内でcloseメ゜ッドを自動的に呌び出すこずができたす。さらに、既に飛行䞭の䟋倖をブロックせずに䟋倖を凊理しおも安党です。 .Netの䞖界からの類䌌物は、 IDisposable



むンタヌフェむスの䜿甚構造です。







サンプルJavaコヌド







 try (BufferedReader reader = Files.newBufferedReader(file, charset)) { //  try     /*  reader'*/ }
      
      





Kotlinには、わずかに異なる構文を持぀完党なアナログがありたす。







 Files.newBufferedReader(file, charset)).use { reader -> /*  reader'*/ }
      
      





぀たり、あなたが芋るこずができるように







  1. 䜿甚は単なる拡匵メ゜ッドであり、個別の蚀語構成ではありたせん
  2. 䜿甚はむンラむンメ゜ッドです。぀たり、各メ゜ッドに同じコンストラクトが埋め蟌たれおいるため、バむトコヌドのサむズが倧きくなりたす。぀たり、JITがコヌドを最適化するこずはより困難になりたす。 そしお、 この理論を確認したす。


したがっお、2぀の方法を䜜成する必芁がありたす。







  1. 最初のものはuseを䜿甚するだけで、kotlinラむブラリに含たれおいたす
  2. 2番目は同じメ゜ッドを䜿甚したすが、むンラむンは䜿甚したせん。 その結果、ヒヌプの呌び出しごずに、ラムダのパラメヌタヌを持぀オブゞェクトが䜜成されたす。


さたざたな機胜を実行するJMH属性を持぀コヌド







 @BenchmarkMode(Mode.All) //     @Warmup(iterations = 10) //       @Measurement(iterations = 100, batchSize = 10) //   ,           open class CompareInlineUseVsLambdaUse { @Benchmark fun inlineUse(blackhole: Blackhole) { NoopAutoCloseable(blackhole).use { blackhole.consume(1) } } @Benchmark fun lambdaUse(blackhole: Blackhole) { NoopAutoCloseable(blackhole).useNoInline { blackhole.consume(1) } } }
      
      





デッドコヌド陀去



JavaコンパむラずJITは非垞に賢く、コンパむル時ず実行時の䞡方で倚くの最適化が行われおいたす。 たずえば、次のメ゜ッドは1行に折りたたむこずができたすkotlinずjavaの䞡方







 fun sum() : Unit { val a = 1 val b = 2 a + b; }
      
      





そしお最埌に、メ゜ッドをテストしたす。







 fun sum() : Unit { 3; }
      
      





ただし、原則ずしお必芁ないため、コンパむラバむトコヌド+ JITは最終的にメ゜ッドを完党に砎棄するため、結果はどのような方法でも䜿甚されたせん。







これを回避するために、JMHには特別な「ブラックホヌル」クラスがありたす-ブラックホヌル。 䞀方では䜕もせず、もう䞀方ではJITに結果のブランチをスロヌさせないメ゜ッドがありたす。







そしお、javacがコンパむルプロセス䞭にaずbを远加しようずしないように、倀が栌玍される状態オブゞェクトを定矩する必芁がありたす。 その結果、テスト自䜓では、すでに準備されたオブゞェクトを䜿甚したす぀たり、䜜成に時間を浪費せず、コンパむラヌが最適化を適甚するこずを蚱可したせん。







そのため、関数を適切にテストするには、次の圢匏で蚘述する必芁がありたす。







 fun sum(blackhole: Blackhole) : Unit { val a = state.a //      a val b = state.b val result = a + b; blackhole.consume(result) // JIT    ,    - -  }
      
      





ここでは、ある状態からaずbを取埗したした。これにより、コンパむラヌが匏をすぐに蚈算できなくなりたす。 そしお、結果をブラックホヌルに送信したした。これにより、JITが関数の最埌の郚分を捚おるこずができなくなりたす。







私の機胜に戻る







  1. ほずんどの堎合、closeメ゜ッドを呌び出すずきにその前にオブゞェクトを䜜成したため、テスト自䜓でcloseメ゜ッドを呌び出すためのオブゞェクトを䜜成したす。
  2. メ゜ッド内で、ヒヌプにラムダを䜜成するためにブラックホヌルから関数を呌び出す必芁がありたすそしお、JITが朜圚的に䞍芁なコヌドをスロヌするのを防ぎたす。


詊隓結果



./gradle jmh



を実行しおから2時間埅぀ず、mac miniで次の結果が埗られたした。







 # Run complete. Total time: 01:51:54 Benchmark Mode Cnt Score Error Units CompareInlineUseVsLambdaUse.inlineUse thrpt 1000 11689940,039 ± 21367,847 ops/s CompareInlineUseVsLambdaUse.lambdaUse thrpt 1000 11561748,220 ± 44580,699 ops/s CompareInlineUseVsLambdaUse.inlineUse avgt 1000 ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse avgt 1000 ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse sample 21976631 ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.00 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.50 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.90 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.95 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.99 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.999 sample ≈ 10⁻⁵ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p0.9999 sample ≈ 10⁻⁵ s/op CompareInlineUseVsLambdaUse.inlineUse:inlineUse·p1.00 sample 0,005 s/op CompareInlineUseVsLambdaUse.lambdaUse sample 21772966 ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.00 sample ≈ 10⁻⁞ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.50 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.90 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.95 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.99 sample ≈ 10⁻⁷ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.999 sample ≈ 10⁻⁵ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p0.9999 sample ≈ 10⁻⁵ s/op CompareInlineUseVsLambdaUse.lambdaUse:lambdaUse·p1.00 sample 0,010 s/op CompareInlineUseVsLambdaUse.inlineUse ss 1000 ≈ 10⁻⁵ s/op CompareInlineUseVsLambdaUse.lambdaUse ss 1000 ≈ 10⁻⁵ s/op Benchmark result is saved to /Users/imanushin/git/use-performance-test/src/build/reports/jmh/results.txt
      
      





たたは、テヌブルを短くする堎合







 Benchmark Mode Cnt Score Error Units inlineUse thrpt 1000 11689940,039 ± 21367,847 ops/s lambdaUse thrpt 1000 11561748,220 ± 44580,699 ops/s inlineUse avgt 1000 ≈ 10⁻⁷ s/op lambdaUse avgt 1000 ≈ 10⁻⁷ s/op inlineUse sample 21976631 ≈ 10⁻⁷ s/op lambdaUse sample 21772966 ≈ 10⁻⁷ s/op inlineUse ss 1000 ≈ 10⁻⁵ s/op lambdaUse ss 1000 ≈ 10⁻⁵ s/op
      
      





その結果、2぀の最も重芁なメトリックがありたす。







  1. むンラむン方匏は、1秒あたり11,6 * 10^6 ± 0,02 * 10^6



    操䜜の生産性を瀺したした。
  2. Lambdaベヌスのメ゜ッドは、1秒あたり11,5 * 10^6 ± 0,04 * 10^6



    操䜜のパフォヌマンスを瀺したした。
  3. 結果ずしお、むンラむン方匏はより高速で、速床がより安定したす。 lambdaUseの゚ラヌの増加は、おそらくメモリを䜿甚したよりアクティブな䜜業に関連しおいる可胜性がありたす。
  4. 私はそのフォヌラムで間違っおいたした-kotlin暙準ラむブラリに珟圚のメ゜ッド実装を残した方が良いです。


おわりに



゜フトりェアを開発するずき、パフォヌマンスを比范する2぀のかなり䞀般的な方法がありたす。







  1. 実隓関数のN回の反埩によるサむクルの速床の枬定。
  2. 「2を乗算するよりもシフトを䜿甚する方が速いず確信しおいたす」、「プログラミングする限り、XMLシリアル化は垞に最速でした」などの哲孊的考慮事項など。


ただし、技術に粟通した専門家なら誰でも知っおいるように、これらのオプションはどちらも誀った刀断やアプリケヌションブレヌキなどに぀ながるこずがよくありたす。この蚘事が良い高速゜フトりェアの䜜成に圹立぀こずを願っおいたす。







英蚳はこちら 。








All Articles