この用語の使用を聞いたことがありますが、それが何を意味するのか完全にはわかりません。
それは正確にそれが缶で言うことを意味します-それはオペレーティングシステムのカーネルへのシステムコールのような「小さな」何かのパフォーマンスを測定しています。
危険なのは、人々がマイクロベンチマークから得たあらゆる結果を使用して最適化を指示する可能性があることです。そして、誰もが知っているように:
小さな効率については忘れる必要があります。たとえば、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
と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つのケースの間に強い線を引くことは無意味です、IMO、そして私は試みません。)
マイクロベンチマークの危険性は、完全に誤解を招く結果をもたらすベンチマークを簡単に作成できることです。 Javaマイクロベンチマークの一般的なトラップは次のとおりです。
ただし、上記の問題に対処した後でも、ベンチマークで対処できないシステム上の問題があります。ベンチマークのコードと動作は、通常、ユーザーが本当に気にしていることとはほとんど関係がありません。つまり、yourアプリケーションの実行方法です。 「隠し変数」が多すぎて、ベンチマークから一般的なプログラム、さらにはプログラムに一般化することができません。
これらの理由により、マイクロベンチマークで時間を無駄にしないように定期的にアドバイスしています。代わりに、シンプルで自然なコードを記述し、プロファイラーを使用して手動で最適化する必要がある領域を特定するのが最善です。興味深いことに、通常、実際のアプリケーションにおける最も重大なパフォーマンスの問題は、典型的なマイクロベンチマークが試みているようなものではなく、データ構造とアルゴリズム(ネットワーキング、データベース、およびスレッド関連のボトルネックを含む)の設計が悪いことが原因であることが判明しています。テスト。
@BalusCは、このトピックに関する資料への優れたリンクを Hotspot FAQ ページに提供しています。そして、これが Brian Goetz によるIBMホワイトペーパーへのリンクです。
1-エキスパートはJavaでハードウェアベンチマークを実行しようとさえしませんでした。バイトコードとハードウェアの間で発生している「複雑なこと」が多すぎて、生の結果からハードウェアに関する有効で有用な結論を引き出すことができません。ハードウェアに近い言語を使用したほうがよいでしょう。例えばCまたはアセンブリコード。
- それは何を意味し、何を意味しませんか?
マイクロベンチマークとは、単に小さなものを測定することを意味します。 Tinyはおそらくコンテキストに依存しますが、通常は単一のシステムコールまたは類似のレベルです。ベンチマークとは、上記のすべてを指します。
- ISとIS N'Tのマイクロベンチマークの例は何ですか?
この記事 は、getpid()システムコールの測定時間とマイクロベンチマークの例としてmemcpy()を使用してメモリをコピーする時間を測定します。
アルゴリズム実装などの測定は、マイクロベンチマークとしてカウントされません。特に、実行時間の短いタスクをリストした結果レポートは、おそらくマイクロベンチマークとしてカウントされません。
- マイクロベンチマークの危険性は何ですか?どのようにそれを回避しますか?
明らかな危険は、プログラムの間違った部分を最適化するよう開発者を誘惑することです。もう1つの危険は、小さなものを正確に測定することが難しいことで有名です。これを回避する最も簡単な方法は、プログラムで最も多くの時間を費やしている場所を正確に把握することです。
人々は通常「マイクロベンチマークを行わない」と言いますが、彼らがおそらく意味することは「マイクロベンチマークに基づいて最適化の決定を行わないこと」です。
- (またはそれは良いことですか?)
それ自体は他の人たちと同じようにそれ自体悪いことではなく、多くのWebページが示唆しているようです。場所があります。私はプログラムの書き換えやランタイムアスペクトウィービングなどを扱っています。通常、追加された命令のマイクロベンチマークを公開しています。最適化のガイドとしてではありませんが、追加のコードが書き換えられたプログラムの実行にほとんど影響を与えないようにします。
ただし、これは特にJIT、ウォームアップ時間などを備えたVMのコンテキストでは芸術です。Javaのよく説明されたアプローチは、ここに記述されています ここ 。
本(Java Performance The Definitive Guide)では、マイクロベンチマークに関するこの定義と例があります
マイクロベンチマーク
マイクロベンチマークは、非常に小さなユニットパフォーマンスを測定するために設計されたテストです。同期メソッドと非同期メソッドを呼び出す時間。スレッドの作成とスレッドプールの割り当てのオーバーヘッド。 1つの算術アルゴリズムと代替実装を実行する時間。等々。
マイクロベンチマークは良い考えのように思えるかもしれませんが、正しく記述することは非常に困難です。次のコードを考慮してください。これは、パフォーマンスをテストするマクロベンチマークを書く試みですod 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));
その結果、経過時間は、fibonaciメソッドの実装、またはループの実行が想定される回数に関係なく、ほんの数ミリ秒になります。
その特定の問題を回避する方法があります。各結果が確実に読み取られるようにしてください。実際には、lの定義をローカル変数からインスタンス変数(volatileキーワードで宣言)に変更すると、メソッドのパフォーマンスを測定できます。
以下は、Javaで(マイクロ)ベンチマークが特に難しい理由を説明するBrian Goetzによる優れた記事です。