Javaで正しいマイクロベンチマークを作成するにはどうすればよいですか?


870

Javaで正しいマイクロベンチマークをどのように作成(および実行)しますか?

考えるべきさまざまなことを示すコードサンプルとコメントを探しています。

例:ベンチマークは、時間/反復または反復/時間を測定する必要がありますか?その理由は?

関連:ストップウォッチのベンチマークは受け入れられますか?


関連情報については、数分前の[この質問] [1]を参照してください。編集:申し訳ありませんが、これは答えではありません。コメントとして投稿すればよかった。[1]:stackoverflow.com/questions/503877/...
ティアゴ

その質問の投稿者をこのような質問に参照することを計画した後、この質問が存在しないことに気付きました。ですから、これがうまくいけば、時間の経過とともにいくつかの良いヒントを集めることができます。
John Nilsson、2011

5
Java 9はマイクロベンチマークのためのいくつかの機能を提供する可能性があります:openjdk.java.net/jeps/230
Raedwald

1
@Raedwald JEPはJDKコードにマイクロベンチマークを追加することを目的としていると思いますが、jmhがJDKに含まれるとは思いません...
assylias

1
@Raedwald未来からこんにちは。うまくいきませんでした
Michael

回答:


787

Java HotSpotの作成者からのマイクロベンチマークの作成に関するヒント:

ルール0: JVMとマイクロベンチマークに関する信頼できる論文を読んでください。良いものは、ブライアン・ゲッツ、2005年です。マイクロベンチマークにあまり期待しないでください。限られた範囲のJVMパフォーマンス特性のみを測定します。

ルール1:常に、テストカーネルを実行するウォームアップフェーズを含めます。これは、タイミングフェーズの前にすべての初期化とコンパイルをトリガーするのに十分です。(ウォームアップフェーズでは反復回数を減らしても問題ありません。経験則では、数万回の内部ループの反復回数です。)

ルール2:常にで実行-XX:+PrintCompilation-verbose:gcあなたはコンパイラやJVMの他の部分は、あなたのタイミング位相中に予期しない仕事をしていないことを確認することができますので、など。

ルール2.1:タイミングフェーズとウォームアップフェーズの最初と最後にメッセージを出力するので、タイミングフェーズ中にルール2からの出力がないことを確認できます。

ルール3:-client-server、およびOSRと通常のコンパイルの違いに注意してください。この-XX:+PrintCompilationフラグは、アットマーク付きのOSRコンパイルを報告して、非初期エントリポイントを示しますTrouble$1::run @ 2 (41 bytes)。次に例を示します。最高のパフォーマンスを求めている場合は、クライアントよりサーバーを優先し、OSRよりも定期的に実行します。

ルール4:初期化の影響に注意してください。印刷するとクラスがロードおよび初期化されるため、タイミングフェーズで初めて印刷しないでください。クラスのロードを具体的にテストする場合(およびその場合はテストクラスのみをロードする場合)を除き、ウォームアップフェーズ(または最終レポートフェーズ)の外で新しいクラスをロードしないでください。ルール2は、そのような影響に対する防御の最前線です。

ルール5:最適化解除と再コンパイルの影響に注意してください。タイミングフェーズでコードパスを初めて使用しないでください。パスがまったく使用されないという以前の楽観的な仮定に基づいて、コンパイラがコードをジャンクして再コンパイルする場合があるためです。ルール2は、そのような影響に対する防御の最前線です。

ルール6:適切なツールを使用してコンパイラーの心を読み、コンパイラーが生成するコードに驚かされることを期待します。何かを速くしたり遅くしたりするものについての理論を形成する前に、自分でコードを調べてください。

ルール7:測定のノイズを減らします。ベンチマークを静かなマシンで実行し、それを数回実行して、外れ値を破棄します。-Xbatchコンパイラーをアプリケーションとシリアル化するために使用し、コンパイラー-XX:CICompilerCount=1がそれ自体と並行して実行されないように設定することを検討してください。GCのオーバーヘッド、セット減らすために最善を試してみてくださいXmx(十分な大きさ)に等しくXmsし、使用しUseEpsilonGC、それが利用可能な場合。

ルール8:おそらくより効率的であり、この唯一の目的のためにすでにデバッグされているため、ベンチマークにはライブラリを使用します。などJMHキャリパービルやJava用ポールの優れたUCSDベンチマーク


5
これも興味深い記事でした:ibm.com/developerworks/java/library/j-jtp12214
John Nilsson

142
また、ほとんどのOSとJVMの組み合わせで一般的な+または-15ミリ秒の精度で問題がない場合を除き、System.currentTimeMillis()を使用しないでください。代わりにSystem.nanoTime()を使用してください。
スコットキャリー


93
System.nanoTime()は、よりも正確であることが保証されていないことに注意してくださいSystem.currentTimeMillis()。少なくとも正確であることが保証されているだけです。ただし、通常はかなり正確です。
重力

41
System.nanoTime()代わりに使用しなければならない主な理由System.currentTimeMillis()は、前者が単調に増加することが保証されているからです。2つのcurrentTimeMillis呼び出しで返された値を減算すると、システム時間が一部のNTPデーモンによって調整されたために、実際には否定的な結果が得られる可能性があります。
Waldheinz

239

この質問に回答済みのマークが付いていることは知っていますが、マイクロベンチマークの作成に役立つ2つのライブラリについて言及したいと思います

Googleのキャリパー

入門チュートリアル

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

OpenJDKのJMH

入門チュートリアル

  1. JVMでのベンチマークの落とし穴の回避
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/

37
+1承認された回答のルール8として追加されている可能性があります。
assylias

8
最近の@Pangea jmhは、おそらくCaliperより優れています。以下
#!msg

86

Javaベンチマークの重要な点は次のとおりです。

  • タイミングの前にコードを数回実行して、最初にJITをウォームアップします。それを
  • 結果を数秒または数十秒(より良い)で測定できるように十分長く実行してください。
  • System.gc()イテレーション間で呼び出すことはできませんが、テスト間で実行することをお勧めします。これにより、各テストで「クリーンな」メモリスペースを使用できるようになります。(はい、gc()保証というよりヒントですが、私の経験では、ガベージコレクションが実際に行われる可能性が非常に高いです。)
  • 反復と時間、および「最良の」アルゴリズムが1.0のスコアを取得し、その他のスコアが相対的にスコア付けされるようにスケーリングできる時間/反復のスコアを表示したい。つまり、反復回数と時間の両方を変化させながら、すべてのアルゴリズムを長い時間実行できますが、同等の結果が得られます。

私は.NETのベンチマークフレームワークの設計についてブログを書いているところです。私が持っているカップル以前の記事あなたにいくつかのアイデアを与えることができるかもしれ-ませんすべてはもちろん、適切であろうが、それの一部であってもよいです。


3
マイナーヒント:IMOは「各テストが取得されるように」は「各テストが取得されるように」であるべきです。 gc 、常に未使用のメモリが解放されますが。
Sanjay T. Sharma

@ SanjayT.Sharma:まあ、意図は実際にそうすることです。厳密に保証されているわけではありませんが、実際にはかなり強力なヒントです。より明確になるように編集します。
Jon Skeet、

1
System.gc()の呼び出しに同意しません。ヒントです、それだけです。「うまくいけば何かをする」ことすらありません。あなたは決してそれを呼ぶべきではありません。これはプログラミングではなく、アートです。
gyorgyabraham 2013年

13
@ギャブラハム:はい、それはヒントです-しかし、それは私が通常取ることが観察されたものです。では、を使用したくない場合はSystem.gc()、前のテストで作成されたオブジェクトが原因で、1つのテストでガベージコレクションを最小限に抑えることをどのように提案しますか?私は実用的で、独断的ではありません。
Jon Skeet 2013年

9
@ギャブラハム:「偉大なフォールバック」とはどういう意味かわかりません。もう一度説明してください。より良い結果を出すための提案はありますか?私はそれが保証ではないと明確に言いました...
ジョン・スキート

48

jmhは最近OpenJDKに追加されたものであり、Oracleの一部のパフォーマンスエンジニアによって記述されています。確かに一見の価値があります。

jmhは、Javaおよびその他のJVMをターゲットとする言語で記述されたnano / micro / macroベンチマークを構築、実行、分析するためのJavaハーネスです。

サンプルテストのコメントに埋め込まれ非常に興味深い情報。

以下も参照してください。


1
JMHの使用開始の詳細については、このブログ投稿:psy-lob-saw.blogspot.com/2013/04/…も参照してください。
Nitsan Wakart 2013年

参考までに、JEP 230:Microbenchmark Suiteは、このJava Microbenchmark Harness(JMH)プロジェクトに基づくOpenJDKの提案です。Java 9のカットはしませんでしたが、後で追加される可能性があります。
バジルブルク

23

ベンチマークは、時間/反復または反復/時間を測定する必要がありますか?その理由は?

テストしようとしているものに依存します。

レイテンシに関心がある場合は時間/反復を使用し、スループットに関心がある場合は反復/時間を使用します。


16

2つのアルゴリズムを比較する場合は、順序を入れ替えて、それぞれに少なくとも2つのベンチマークを実行します。つまり:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

異なるパスで同じアルゴリズムのランタイムにいくつかの顕著な違い(5-10%時々)を発見しました。

また、各ループの実行時間が少なくとも10秒程度になるように、nが非常に大きいことを確認してください。反復が多いほど、ベンチマーク時間の数値が大きくなり、データの信頼性が高くなります。


5
当然、順序を変更するとランタイムに影響します。ここでは、JVM最適化とキャッシング効果が機能します。より良いのは、JVM最適化を「ウォームアップ」し、複数の実行を実行し、異なるJVMですべてのテストをベンチマークすることです。
Mnementh、2009

15

ベンチマークされたコードで計算された結果を何らかの方法で使用していることを確認してください。それ以外の場合は、コードを最適化することができます。


13

Javaでマイクロベンチマークを記述する場合、多くの落とし穴があります。

最初に、多かれ少なかれランダムな時間がかかるすべての種類のイベントで計算する必要があります:ガベージコレクション、キャッシュ効果(ファイルのOSとメモリのCPUの)、IOなど。

2番目:非常に短い間隔で測定された時間の正確さは信頼できません。

3番目:JVMは実行中にコードを最適化します。したがって、同じJVMインスタンスでの異なる実行は、ますます速くなります。

私の推奨事項:ベンチマークを数秒実行します。これは、ミリ秒を超えるランタイムよりも信頼性が高くなります。JVMをウォームアップします(JVMが最適化を実行できることを測定せずにベンチマークを少なくとも1回実行することを意味します)。そして、ベンチマークを複数回(おそらく5回)実行し、中央値を取得します。新しいJVMインスタンスですべてのマイクロベンチマークを実行します(すべてのベンチマークの新しいJavaを呼び出します)。そうしないと、JVMの最適化の影響が後で実行されるテストに影響を与える可能性があります。ウォームアップフェーズでは実行されないものを実行しないでください(クラスロードと再コンパイルがトリガーされる可能性があるため)。


8

また、異なる実装を比較する場合は、マイクロベンチマークの結果を分析することが重要になる場合があることにも注意してください。したがって、有意性検定行う必要があります。

これはA、ベンチマークのほとんどの実行中に、実装よりも実装の方が高速になる可能性があるためBです。ただしA、スプレッドも大きい可能性があるため、の測定されたパフォーマンス上の利点はABた。

したがって、マイクロベンチマークを正しく記述して実行することも重要ですが、正しく分析することも重要です。


8

他の優れたアドバイスに加えて、私は次のことにも注意します。

一部のCPU(TurboBoostを搭載したIntel Core i5範囲など)では、温度(および現在使用されているコアの数、および使用率)がクロック速度に影響します。CPUは動的にクロックされるため、結果に影響を与える可能性があります。たとえば、シングルスレッドアプリケーションの場合、最大クロック速度(TurboBoostを使用)は、すべてのコアを使用するアプリケーションよりも高速です。したがって、これは一部のシステムでのシングルスレッドとマルチスレッドのパフォーマンスの比較を妨げる可能性があります。温度とボラタジはターボ周波数が維持される期間にも影響することに注意してください。

おそらく、あなたが直接制御できる、より根本的に重要な側面です。正しいものを測定していることを確認してください!たとえばSystem.nanoTime()、特定のコードのベンチマークに使用している場合は、不要なものを測定しないように、意味のある場所に割り当ての呼び出しを配置し​​ます。たとえば、次のことは行わないでください。

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

問題は、コードが終了したときにすぐに終了時刻を取得していないことです。代わりに、以下を試してください。

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");

はい、時限領域内で無関係な作業を行わないことが重要ですが、最初の例はまだ問題ありません。への呼び出しは1つだけでprintln、個別のヘッダー行などではなく、その呼び出しの文字列引数を構築する最初のステップSystem.nanoTime()として評価する必要があります。コンパイラーが最初のものでできることは2番目のものではできないことは何もありませんし、停止時間を記録する前に余分な作業をするように奨励することもできません。
Peter Cordes

7

http://opt.sourceforge.net/ Java Micro Benchmark-異なるプラットフォーム上のコンピューターシステムの比較パフォーマンス特性を決定するために必要な制御タスク。最適化の決定を導き、さまざまなJava実装を比較するために使用できます。


2
Javaコードの任意の部分ではなく、JVM +ハードウェアをベンチマークするだけのようです。
ステファンL
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.