どのようにしてJavaで正しいマイクロベンチマークを書いて(そして実行して)いますか?
さまざまなことを説明するためのコードサンプルとコメントを探しています。
例:ベンチマークでは時間/反復または反復/時間を測定する必要がありますが、その理由は何ですか。
マイクロベンチマークを書くためのヒント Java HotSpotの作者による :
規則0:JVMとマイクロベンチマークに関する評判の良い論文を読んでください。良いものは Brian Goetz、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 、 Caliper 、または BillおよびPaulによるJava用の優れたUCSDベンチマーク など。
私はこの質問が回答済みとしてマークされていることを知っていますが、私たちがマイクロベンチマークを書くのを助ける2つのライブラリに言及したいと思いました
チュートリアルを始める
チュートリアルを始める
Javaベンチマークで重要なことは以下のとおりです。
System.gc()
を呼び出すことはできませんが、テスト間で実行することをお勧めします。そうすることで、各テストでうまく動作するように「クリーンな」メモリスペースを確保できるようになります。 (はい、gc()
は単なる保証ではありませんが、私の経験では実際にガベージコレクションを行う可能性が非常に高い可能性があります。)私は、.NETのベンチマークフレームワークの設計についてブログを書いているところです。私は couple of 以前の投稿 を持っています - それはあなたにいくつかのアイデアを与えることができるかもしれません - もちろんすべてが適切になるとは限りませんが、それのいくつかはそうかもしれません。
jmh は最近OpenJDKに追加されたもので、Oracleのパフォーマンスエンジニアによって書かれました。一見の価値があります。
Jmhは、JavaおよびJVMをターゲットとする他の言語で書かれたナノ/マイクロ/マクロのベンチマークを構築、実行、および分析するためのJavaハーネスです。
非常に興味深い情報が埋め込まれています サンプルテストのコメント 。
また見なさい:
ベンチマークは時間/反復または反復/時間を測定する必要がありますか、そしてその理由は何ですか?
それはあなたがテストしようとしているwhatに依存します。
もしあなたが待ち時間に興味があれば、time/iterationを使ってください。もしthroughputに興味があれば、iterations/timeを使ってください。
ベンチマークコードで計算された結果をどうにかして使用するようにしてください。それ以外の場合は、コードを最適化することができます。
2つのアルゴリズムを比較しようとしている場合は、それぞれについて少なくとも2つのベンチマークを行い、順番を入れ替えます。すなわち:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
私は、異なるパスで同じアルゴリズムの実行時間にいくつかの顕著な違い(時々5-10%)を発見しました。
また、nが非常に大きいことを確認して、各ループの実行時間が少なくとも10秒程度になるようにします。反復回数が多いほど、ベンチマーク時間の重要な数字が多くなり、データの信頼性が高まります。
Javaでマイクロベンチマークを書くことには多くの落とし穴があります。
最初:多少なりともランダムに時間がかかるあらゆる種類のイベントで計算する必要があります。ガベージコレクション、キャッシュ効果(ファイルの場合はOS、メモリの場合はCPU)、IOなど。
第二:あなたは非常に短い間隔のための測定時間の正確さを信頼することはできません。
3番目:JVMは実行中にコードを最適化します。そのため、同じJVMインスタンス内での異なる実行はどんどん速くなります。
私の推奨事項:ベンチマークを数秒で実行するようにします。これは、ランタイムよりも数ミリ秒以上信頼性が高いです。 JVMをウォームアップします(JVMが最適化を実行できることを測定なしで少なくとも1回ベンチマークを実行することを意味します)。そしてベンチマークを複数回(たぶん5回)実行して中央値を取ります。すべてのマイクロベンチマークを新しいJVMインスタンスで実行します(すべてのベンチマークに新しいJavaを要求します)。そうしないと、JVMの最適化効果が後で実行されるテストに影響を与える可能性があります。実行しないでください。ウォームアップフェーズでは実行されません(これがクラスロードと再コンパイルを引き起こす可能性があるため)。
異なる実装を比較するときには、マイクロベンチマークの結果を分析することも重要である可能性があることにも注意してください。したがって、 有意検定 を作るべきです。
これは、ベンチマークのほとんどの実行中に、実装A
が実装B
よりも高速である可能性があるためです。しかし、A
のスプレッドも高くなる可能性があるため、A
の測定されたパフォーマンス上の利点は、B
と比較しても意味がありません。
したがって、マイクロベンチマークを正しく作成して実行するだけでなく、正しく分析することも重要です。
他の優れたアドバイスに追加するには、私はまた次のことに留意する必要があります:
一部の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");
http://opt.sourceforge.net/ Java Micro Benchmark - 異なるプラットフォーム上のコンピュータシステムの比較パフォーマンス特性を決定するために必要な制御タスク。最適化の決定を導き、異なるJava実装を比較するために使用できます。