私はそこにあるほとんどのマイクロ最適化を理解していますが、それらは本当に役に立ちますか?
例:++i
の代わりに実行するi++
か、while(1)
またはfor(;;)
実際にパフォーマンスの向上(メモリフィンガープリントまたはCPUサイクルのいずれか)をもたらしますか?
だから問題は、Cでどのようなマイクロ最適化を行うことができるかということです。それらは本当に便利ですか?
私はそこにあるほとんどのマイクロ最適化を理解していますが、それらは本当に役に立ちますか?
例:++i
の代わりに実行するi++
か、while(1)
またはfor(;;)
実際にパフォーマンスの向上(メモリフィンガープリントまたはCPUサイクルのいずれか)をもたらしますか?
だから問題は、Cでどのようなマイクロ最適化を行うことができるかということです。それらは本当に便利ですか?
このようなものを最適化するには、コンパイラに依存する必要があります。適切なアルゴリズムを使用し、信頼性が高く、読みやすく、保守しやすいコードを書くことに集中してください。
最も遅いスクリプト言語の 1 つである Tcl で記述された Web サーバーである tclhttpd が、おそらく最速のコンパイル言語の 1 つである C で記述された Web サーバーである Apache よりも優れたパフォーマンスを発揮した日は、比較するとマイクロ最適化が大幅に見劣りすると確信した日でした。より高速なアルゴリズム/技術を使用する*。
デバッガーでそれが問題であることを証明できるまで、マイクロ最適化について心配する必要はありません。それでも、まずここに来て、誰かがあなたにそうしないように説得してくれることを願って、それが良い考えかどうか尋ねることをお勧めします.
これは直観に反しますが、多くの場合、コードを削除するのではなく、コードを追加することで、特に密にネストされたループや再帰を最適化します。ゲーム業界は、フィルターを使用してネストされたループを高速化し、不要な処理を回避する無数のトリックを考え出しました。これらのフィルターは、i++ と ++i の違いよりもはるかに多くの命令を追加します。
*注: それ以来、私たちは多くのことを学びました。スレッドの生成にはコストがかかるため、遅いスクリプト言語はコンパイルされたマシン コードよりもパフォーマンスが高いという認識が、lighttpd、NginX、および Apache2 の開発につながりました。
これらのマイクロ最適化のほとんどはコンパイラーによって行われるため、これらのマイクロ最適化について考える必要はないと思います。これらのことは、コードを読みにくくするだけです。
[編集済み]時期尚早の[/編集済み]最適化は悪であることを忘れないでください。
マイクロ最適化、トリック、何かを行う代替手段には違いがあると思います。++i
の代わりにマイクロ最適化を使用することもできますが、i++
事前にインクリメント (またはデクリメント) すると、コンパイラは変数の現在の値を追跡するためにコードを挿入する必要がないため、ペシミゼーションを回避するだけだと思います。表現に使用します。プレインクリメント/デクリメントを使用しても式のセマンティクスが変わらない場合は、それを使用してオーバーヘッドを回避する必要があります。
一方、トリックとは、明白でないメカニズムを使用して、単純なメカニズムよりも速く結果を達成するコードです。絶対に必要でない限り、トリックは避けるべきです。わずかな割合の高速化は、そのわずかな割合が意味のある時間を反映していない限り、一般にコードの可読性を損なう価値はありません。非常に長時間実行されるプログラム、特に計算量の多いプログラム、またはリアルタイム プログラムは、システム パフォーマンスの目標を達成するために節約された時間が必要になる可能性があるため、多くの場合トリックの候補になります。トリックを使用する場合は、明確に文書化する必要があります。
代替案は、それだけです。パフォーマンスがまったく向上しないか、ほとんど向上しない場合があります。それらは、同じ意図を表現する 2 つの異なる方法を表しているだけです。コンパイラは同じコードを生成することさえあります。この場合、最も読みやすい式を選択します。多少のパフォーマンスの低下が生じたとしても、そうすることをお勧めします (ただし、前の段落を参照してください)。
2 つの異なるコード シーケンスが同じ結果を生成することが簡単にわかる場合、コード内に存在するもの以外のデータについて仮定を行うことなく、コンパイラもそれを行うことができ、通常はそうします。
一方から他方への変換が非常に自明ではない場合、または真実であることがわかっている可能性があることを想定する必要がある場合にのみ、コンパイラーには推論する方法がありません (たとえば、操作がオーバーフローできない、または 2 つのポインターがエイリアスにならないなど)。キーワードで宣言されていなくてもrestrict
)、これらのことについて考えるのに時間を費やす必要があります。その場合でも、通常、行うべき最善の方法は、コンパイラが行うことができる仮定についてコンパイラに通知する方法を見つけることです。
コンパイラが単純な変換を見落としている特定のケースを見つけた場合、99% の確率で、コンパイラに対してバグを報告し、より重要なことに取り掛かる必要があります。
正直なところ、その質問は有効ですが、今日では関係ありません。なぜですか?
コンパイラの作成者は 20 年前よりもはるかに賢くなっており、過去にさかのぼります。その後、これらの最適化は非常に重要でした。私たちは皆、古い 80286/386 プロセッサで作業していました。コーダーは、さらに多くを圧縮するためのトリックに頼ることがよくありました。コンパイルされたコードからバイト。
今日、プロセッサは速すぎます。コンパイラの作成者はオペランド命令の詳細を熟知しており、すべてを機能させる必要があります。運が良ければ、8Mb が優れていると考えられていました!!
パラダイムは変化し、コンパイルされたコードからすべてのバイトを絞り出すことについてでしたが、現在はプログラマーの生産性と、リリースをより早くリリースすることに重点が置かれています。
上記では、プロセッサとコンパイラの性質について説明しましたが、Intel 80x86 プロセッサ ファミリ、Borland/Microsoft コンパイラについて話していました。
これがお役に立てば幸いです。よろしくお願いします、トム。
メモリが新しいディスクであるという事実を念頭に置くと、これらのマイクロ最適化を適用するよりもはるかにパフォーマンスが向上する可能性があります。
++iとi++の問題についてもう少し実用的な見方をする場合(少なくともC ++のコンテキストでは)、http://llvm.org/docs/CodingStandards.html#micro_preincrementを参照してください。
クリス・ラトナーがそれを言うなら、私は注意を払わなければなりません。;-)
あなたが書くすべてのプログラムは、主に、バグ修正、再利用、理解を必要とする他の人間に自分のアイデア、意図、および推論を伝えるための言語であると考えたほうがよいでしょう。彼らは、コンパイラやランタイム システムがコードを実行するよりも、文字化けしたコードのデコードに多くの時間を費やします。要約すると、問題の言語の一般的なイディオムを使用して、最も明確な方法で言いたいことを言います。
C でのこれらの特定の例では、for(;;) は無限ループのイディオムであり、"i++" は、式で値を使用しない限り、"add one to i" の通常のイディオムです。最も明確な意味を持つ値は、インクリメントの前後の値です。
私の経験では、これが本当の最適化です。
SO の誰かが、マイクロ最適化は「体重を減らすために散髪する」ようなものだと言ったことがあります。アメリカのテレビで「The Biggest Loser」という番組があり、肥満の人たちが減量を競います。体重を数グラムまで減らすことができれば、ヘアカットが役立つでしょう.
マイクロ最適化が実際に違いを生むコードを見た(そして書いた)ので、おそらくそれはマイクロ最適化への類推を誇張しているかもしれませんが、最初は、あなたが知らない問題を単に解決しないことによって得られることがもっとたくさんあります持ってる。
x ^= y
y ^= x
x ^= y
一般に、ゼロに向かってカウントするループは、他の数に向かってカウントするループよりも高速です。コンパイラがこの最適化を行うことができない状況を想像できますが、自分で行うことはできます。
長さ x の配列があり、x は非常に大きな数であり、x の各要素に対して何らかの操作を実行する必要があるとします。さらに、これらの操作がどの順序で発生するかは気にしないとしましょう。これを行うかもしれません...
int i;
for (i = 0; i < x; i++)
doStuff(array[i]);
ただし、代わりにこの方法で行うことで、少し最適化できます-
int i;
for (i = x-1; i != 0; i--)
{
doStuff(array[i]);
}
doStuff(array[0]);
コンパイラは、順序が重要でないと想定できないため、これを行いません。
MaR のサンプル コードの方が優れています。doStuff() が int を返すと仮定すると、次のようになります。
int i = x;
while (i != 0)
{
--i;
printf("%d\n",doStuff(array[i]));
}
配列の内容を逆順で出力することが許容される限り、これは問題ありませんが、コンパイラはそれを決定できません。
これは最適化であるため、ハードウェアに依存します。アセンブラーの作成について (何年も前に) 覚えていることから、ゼロまでカウントダウンするのではなく、カウントアップするには、ループを通過するたびに追加の機械語命令が必要になります。
テストが (x < y) のようなものである場合、テストの評価は次のようになります。
テストが ( x != 0) の場合、これを行うことができます。
反復ごとに減算命令をスキップできます。
減算命令で減算の結果に基づいてフラグを設定できるアーキテクチャがありますが、x86 はそれらの 1 つではないことは確かです。機械命令。
++i は、可能な最適化ではなく、実行しようとしていること (i をインクリメントする) のセマンティクスをより適切に表すため、戻り値を使用しない状況では i++ よりも優先する必要があります (少し高速になる可能性があり、おそらく悪くはない)。