どのようにしてJavaで正しいマイクロベンチマークを書いて(そして実行して)いますか?
さまざまなことを説明するためのコードサンプルとコメントをここで探しています。
例:ベンチマークでは時間/反復または反復/時間を測定する必要がありますが、その理由は何ですか。
マイクロベンチマークを書くためのヒントJava HotSpotの作成者から:
規則0:JVMとマイクロベンチマークに関する評判の良い論文を読んでください。良いものは2005年ブライアン・ゲッツ。マイクロベンチマークからあまり期待しないでください。それらは限られた範囲のJVMパフォーマンス特性のみを測定します。
規則1:タイミングフェーズの前にすべての初期化とコンパイルをトリガーするのに十分な、テストカーネルを最後まで実行するウォームアップフェーズを必ず含めてください。 (ウォームアップ段階での反復回数は少なくても大丈夫です。経験則では、数万回の内部ループ反復が行われます。)
規則2:常に実行-XX:+PrintCompilation
、-verbose:gc
そのため、タイミング段階でコンパイラやJVMの他の部分が予期しない動作をしていないことを確認できます。
規則2.1タイミングフェーズとウォームアップフェーズの最初と最後にメッセージを印刷するので、タイミングフェーズ中にルール2からの出力がないことを確認できます。
規則3:-clientと-server、およびOSRと通常のコンパイルの違いに注意してください。の-XX:+PrintCompilation
flagは、初期ではないエントリポイントを示すために、アットマークを付けて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ベンチマーク。
System.nanoTime()
ではない保証されたより正確にSystem.currentTimeMillis()
。それは少なくとも同じくらい正確であることだけが保証されています。しかし、通常はかなり正確です。 - GravitySystem.nanoTime()
の代わりにSystem.currentTimeMillis()
前者は単調に増加することが保証されているということです。 2を返した値を減算currentTimeMillis
おそらくシステム時間がNTPデーモンによって調整されたためである。 - Waldheinz
私はこの質問が回答済みとしてマークされていることを知っていますが、私たちがマイクロベンチマークを書くことを可能にする2つのライブラリに言及したいと思いました
チュートリアルを始める
チュートリアルを始める
Javaベンチマークで重要なことは以下のとおりです。
System.gc()
繰り返しの間は、テストの間に実行することをお勧めします。そうすることで、各テストでうまく動作するように「クリーンな」メモリスペースを確保できるようになります。 (はい、gc()
保証というよりはヒントですが、非常にありそうなそれは私の経験では本当にガベージコレクションになるでしょう。)私はちょうど.NETのベンチマークフレームワークの設計についてブログを書いているところです。私は持っていますカップルの以前の投稿それはあなたにいくつかのアイデアを与えることができるかもしれません - もちろんすべてが適切であるというわけではありませんが、それのいくつかはそうかもしれません。
gc
常に未使用のメモリを解放します。 - Sanjay T. SharmaSystem.gc()
、以前のテストで作成されたオブジェクトが原因で、1回のテストでガベージコレクションを最小限に抑えることをどのように提案しますか。私は実用的で、独断的ではありません。 - Jon Skeet
jmhは最近OpenJDKに追加されたもので、オラクルの一部のパフォーマンスエンジニアによって書かれました。確かに一見の価値があります。
jmhは、JavaおよびJVMをターゲットとする他の言語で記述されたナノ/マイクロ/マクロのベンチマークを構築、実行、および分析するためのJavaハーネスです。
非常に興味深い情報が埋められていますサンプルテストのコメント。
また見なさい:
ベンチマークは時間/反復または反復/時間を測定する必要がありますか、そしてその理由は何ですか?
テストしようとしている内容によって異なります。待ち時間に関心がある場合は時間/反復を使用し、スループットに関心がある場合は反復/時間を使用してください。
ベンチマークコードで計算された結果をどうにかして使用するようにしてください。さもなければあなたのコードは離れて最適化することができます。
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
。
そのため、マイクロベンチマークを正しく作成して実行するだけでなく、正しく分析することも重要です。
http://opt.sourceforge.net/Java Micro Benchmark - さまざまなプラットフォーム上のコンピュータシステムの比較パフォーマンス特性を決定するために必要な制御タスク。最適化の決定を導き、異なるJava実装を比較するために使用できます。
他の優れたアドバイスに追加するには、私はまた次のことに留意する必要があります。
一部の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");