この用語が使われているのを聞いたことがありますが、それが何を意味するのか完全にはわかりません。
- それは何を意味し、何を意味しないのでしょうか?
- IS と ISN'T のマイクロベンチマークの例をいくつか教えてください。
- マイクロベンチマークの危険性とそれを回避する方法
- (それとも良いことですか?)
この用語が使われているのを聞いたことがありますが、それが何を意味するのか完全にはわかりません。
これは、缶に書かれているとおり、オペレーティング システムのカーネルへのシステム コールなど、「小さな」もののパフォーマンスを測定することを意味します。
危険なのは、人々がマイクロベンチマークから得た結果を使用して、最適化を決定する可能性があることです。そして、私たち全員が知っているように:
約 97% の確率で、わずかな効率性を忘れる必要があります。時期尚早の最適化は諸悪の根源です」 -- Donald Knuth
マイクロベンチマークの結果をゆがめる多くの要因が考えられます。コンパイラの最適化もその 1 つです。測定対象の操作にかかる時間が非常に短いため、測定に使用するものが実際の操作自体よりも長くかかる場合、マイクロベンチマークも歪められます。
たとえば、誰かがfor
ループのオーバーヘッドのマイクロベンチマークを取るかもしれません:
void TestForLoop()
{
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}
明らかに、コンパイラは、ループがまったく何もせず、ループのコードをまったく生成しないことを確認できます。したがって、elapsed
andの値elapsedPerIteration
はほとんど役に立ちません。
ループが何かを行う場合でも:
void TestForLoop()
{
int sum = 0;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
++sum;
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}
コンパイラは、変数sum
が何にも使用されないことを確認して最適化し、for ループも最適化します。ちょっと待って!これを行うとどうなりますか:
void TestForLoop()
{
int sum = 0;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
++sum;
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
printf("Sum: %d\n", sum); // Added
}
コンパイラは、それsum
が常に一定の値であることを認識し、それをすべて最適化するのに十分賢いかもしれません。最近のコンパイラの最適化機能には、多くの人が驚くでしょう。
しかし、コンパイラが最適化して除去できないものについてはどうでしょうか?
void TestFileOpenPerformance()
{
FILE* file = NULL;
time start = GetTime();
for(int i = 0; i < 1000000000; ++i)
{
file = fopen("testfile.dat");
fclose(file);
}
time elapsed = GetTime() - start;
time elapsedPerIteration = elapsed / 1000000000;
printf("Time elapsed for each file open: %d\n", elapsedPerIteration);
}
これでも有用なテストではありません。オペレーティング システムは、ファイルが非常に頻繁に開かれていることを認識する場合があるため、パフォーマンスを向上させるためにファイルをメモリにプリロードする場合があります。ほとんどすべてのオペレーティング システムがこれを行います。アプリケーションを開くときも同じことが起こります。オペレーティング システムは、最も頻繁に開いている上位 5 個のアプリケーションを特定し、コンピューターの起動時にアプリケーション コードをメモリにプリロードします。
実際、参照の局所性 (例: 配列とリンクされたリスト)、キャッシュとメモリ帯域幅の影響、コンパイラのインライン化、コンパイラの実装、コンパイラの切り替え、プロセッサ コアの数、プロセッサ レベルでの最適化など、無数の変数が関与します。 、オペレーティング システムのスケジューラ、オペレーティング システムのバックグラウンド プロセスなど。
そのため、多くの場合、マイクロベンチマークは有用な指標とは言えません。プログラム全体のベンチマークを明確に定義されたテスト ケース (プロファイリング) に置き換えるものではありません。最初に読み取り可能なコードを記述し、次にプロファイルを作成して、必要に応じて何を行う必要があるかを確認します。
マイクロベンチマーク自体は悪ではないことを強調したいと思いますが、慎重に使用する必要があります (これは、コンピューターに関連する他の多くのことにも当てはまります)。
マイクロベンチマークの定義はありませんが、私が使用する場合、特定のハードウェア1または言語機能のパフォーマンスをテストするために設計された小さな人工的なベンチマークを意味します。対照的に、より優れたベンチマークは、実際のタスクを実行するように設計された実際のプログラムです。(この 2 つのケースを明確に区別するのは無意味です。
マイクロ ベンチマークの危険性は、完全に誤解を招く結果をもたらすベンチマークを簡単に作成できることです。Java マイクロベンチマークの一般的なトラップは次のとおりです。
ただし、上記の問題に対処したとしても、対処が不可能なベンチマークに関する体系的な問題があります。通常、ベンチマークのコードと動作は、実際に関心のあることとはほとんど関係がありません。つまり、アプリケーションがどのように実行されるかです。ベンチマークから一般的なプログラム、さらには自分のプログラムまでを一般化するには、「隠れた変数」が多すぎます。
これらの理由から、マイクロベンチマークで時間を無駄にしないよう定期的にアドバイスしています。代わりに、単純で自然なコードを記述し、プロファイラーを使用して手動で最適化する必要がある領域を特定することをお勧めします。興味深いことに、実際のアプリケーションにおける最も重大なパフォーマンスの問題は、通常、典型的なマイクロベンチマークが試みているようなものではなく、データ構造とアルゴリズム (ネットワーク、データベース、およびスレッド関連のボトルネックを含む) の不適切な設計によるものであることが判明しています。テスト。
@BalusC は、Hotspot FAQページでこのトピックに関する資料への優れたリンクを提供しています。Brian Goetzによる IBM ホワイトペーパーへのリンクは次のとおりです。
1 - 専門家は、Java でハードウェアのベンチマークを行おうとさえしません。生の結果からハードウェアに関する有効/有用な結論を導き出すには、バイトコードとハードウェアの間であまりにも多くの「複雑な事柄」が発生しています。ハードウェアに近い言語を使用する方がよいでしょう。たとえば、C またはアセンブリ コードですらあります。
- それはどういう意味ですか、それはどういう意味ですか?
マイクロベンチマークとは、単に小さなものを測定することを意味します。Tinyはおそらくコンテキストに依存しますが、通常は単一のシステムコールなどのレベルに依存します。ベンチマークとは、上記のすべてを指します。
- マイクロベンチマークとは何かの例をいくつか挙げてください。
この(アーカイブされた)記事には、getpid()システムコールの測定時間と、マイクロベンチマークの例としてmemcpy()を使用したメモリのコピー時間の測定がリストされています。
アルゴリズムの実装などの測定は、マイクロベンチマークとしてカウントされません。特に、実行時間が短縮されたタスクを一覧表示する結果レポートは、マイクロベンチマークとしてカウントされることはめったにありません。
- マイクロベンチマークの危険性と、それをどのように回避しますか?
明らかな危険は、開発者がプログラムの間違った部分を最適化するように誘惑することです。もう1つの危険は、小さなものを正確に測定することが難しいことで有名です。これを回避する最も簡単な方法は、おそらく、プログラムで最も多くの時間が費やされている場所を正確に把握することです。
人々は通常「マイクロベンチマークを行わない」と言いますが、おそらく彼らが意味するのは「マイクロベンチマークに基づいて最適化の決定を行わない」ということです。
- (またはそれは良いことですか?)
ここにある他の人たちのように、それ自体はまったく悪いことではなく、多くのWebページが示唆しているようです。それは場所があります。私はプログラムの書き換えやランタイムアスペクトウィービングなどを扱っています。通常、最適化をガイドするためではなく、追加された命令のマイクロベンチマークを公開しますが、追加のコードが書き換えられたプログラムの実行にほとんど影響を与えないようにします。
ただし、これは芸術であり、特にJIT、ウォームアップ時間などがあるVMのコンテキストでは、芸術です。Javaの詳細に説明されているアプローチについては、ここで説明します(アーカイブ)。
本「Java Performance: The Definitive Guide」には、マイクロベンチマークに関する次の定義と例があります。
マイクロベンチマーク
マイクロベンチマークは、非常に小さな単位のパフォーマンスを測定するように設計されたテストです。つまり、同期メソッドと非同期メソッドを呼び出す時間。スレッドプールを使用する場合とスレッドを作成する場合のオーバーヘッド。1 つの算術アルゴリズムと代替実装の実行時間。等々。
マイクロベンチマークは良いアイデアのように思えるかもしれませんが、正しく記述するのは非常に困難です。次のコードを検討してください。これは、50 番目のフィボナッチ数を計算するメソッドのさまざまな実装のパフォーマンスをテストするマイクロベンチマークを作成する試みです。
public void doTest(){ double l; long then = System.currentTimeMillis(); for(int i = 0; i < nLoops; i++){ l = fibImpl1(50); } long now = system.currentTimeMillis(); System.out.println("Elapsed time: " + (now - then)) } ... private double fibImpl1(int n){ if(n < 0) throw new IllegalArgumentException("Must be > 0"); if(n == 0) return 0d; if(n == 1) return 1d; double d = fibImpl1(n - 2) + fibImpl(n - 1); if(Double.isInfinited(d)) throw new ArithmeticException("Overflow"); return d; }
マイクロベンチマークはその結果を使用する必要があります。
このコードの最大の問題は、プログラムの状態を実際に変更しないことです。フィボナッチ計算の結果は使用されないため、コンパイラはその計算を自由に破棄できます。スマート コンパイラ (現在の Java 7 および 8 コンパイラを含む) は、最終的に次のコードを実行します。
long then = System.currentTimeMillis(); long now = System.currentTimeMillis(); System.out.println("Elapsed time: " + (now - then));
その結果、フィボナッチ法の実装やループの実行回数に関係なく、経過時間はわずか数ミリ秒になります。
その特定の問題を回避する方法があります。各結果が読み取られるだけでなく、単に書き込まれることを確認してください。実際には、l の定義をローカル変数からインスタンス変数 (volatile キーワードで宣言) に変更すると、メソッドのパフォーマンスを測定できます。
これは、Javaで(マイクロ)ベンチマークが特に難しい理由を説明するBrianGoetzによるいくつかの優れた記事です。
マイクロベンチマークは、価値があるとは思わないベンチマークです。効果的なベンチマークは、時間をかける価値があると私が考えるベンチマークです。
一般的に言えば、マイクロベンチマークは (インシリコが言うように) 非常に粒度の高いタスクのパフォーマンスを測定しようとするものであり、これをうまく行うのは難しく、実際のパフォーマンスの頭痛のコンテキストでは通常は無意味です。