web-dev-qa-db-ja.com

GCCループ展開フラグは本当に効果的ですか?

Cでは、2次元配列(配列の配列)として割り当てられたhuge行列を使用して、乗算、反転、転置、加算などを行う必要があるタスクがあります。

Gccフラグ-funroll-all-loopsを見つけました。私が正しく理解していれば、プログラマーの努力なしにすべてのループが自動的に展開されます。

私の質問:

a) gccには、-O1-O2などのさまざまな最適化フラグによるこの種の最適化が含まれていますか?

b)ループの展開を利用するためにコード内でpragmasを使用する必要がありますか、それともループが自動的に識別されますか?

c)アンロールによってパフォーマンスが向上する場合、このオプションがデフォルトで有効にならないのはなぜですか?

d)プログラムを可能な限り最良の方法でコンパイルするために推奨されるgcc最適化フラグは何ですか? (私は、コードをコンパイルするマシンと同じ、単一のCPUファミリー向けに最適化されたこのプログラムを実行する必要があります。実際には、march=nativeフラグと-O2フラグを使用します)

編集

場合によってはパフォーマンスを低下させる可能性のあるアンロールの使用について論争があるようです。私の状況では、膨大な数の要素に対して実行される反復行列要素に対して、2つの入れ子のサイクルで単純に数学演算を行うさまざまな方法があります。このシナリオでは、アンロールによってパフォーマンスがどのように低下​​または向上する可能性がありますか?

19
AndreaF

なぜループを展開するのですか?

最新のプロセッサパイプライン命令。彼らは次に何が来るかを知り、命令を実行する順序の仮定に基づいてあらゆる種類の空想的な最適化を行います。

ただし、ループの最後には2つの可能性があります。トップに戻るか、続行します。プロセッサーは、何が起こるかについて知識に基づいた推測を行います。それが正しければ、すべてが良好です。そうでない場合は、パイプラインをフラッシュして、もう一方の分岐を取る準備をしている間、少しストールする必要があります。

ご想像のとおり、ループをアンロールすると、特にオッズが推測に反する場合に、ブランチとそれらのストールの可能性がなくなります。

3回実行してから続行するコードのループを想像してください。 (おそらくプロセッサのように)最後にループを繰り返すと仮定した場合。時間の2/3、あなたは正しいでしょう!ただし、1/3の時間でストールします。

一方、同じ状況を想像してみてください。ただし、コードは3000回ループします。ここでは、おそらく展開からの時間の1/3000の利益しかありません。

なぜループを展開しないのですか?

上記のプロセッサーの空想の一部には、メモリー内の実行可能ファイルからプロセッサーのオンボード命令キャッシュ(Iキャッシュに短縮)に命令をロードすることが含まれます。これは、すばやくアクセスできる限られた量の命令を保持しますが、新しい命令をメモリからロードする必要があるときに停止する可能性があります。

前の例に戻りましょう。ループ内のかなり少量のコードがIキャッシュのnバイトを占めると想定します。ループを展開すると、n * 3バイト。もう少しですが、おそらく単一のキャッシュラインにうまく収まるので、キャッシュは最適に機能し、メインメモリからの読み取りを停止する必要はありません。

ただし、3000ループは、展開してなんとn * 3000バイトのIキャッシュ。それには、メモリからのいくつかの読み取りが必要になり、おそらくプログラムの他の場所からいくつかの他の有用なものをIキャッシュからプッシュします。

だから私は何をしますか?

ご覧のように、アンロールは短いループに対してより多くの利点を提供しますが、多数回ループすることを意図している場合、パフォーマンスが低下します。

通常、スマートコンパイラはどのループを展開するかについて適切な推測をしますが、確信している場合は強制することができます。どのようにしてよりよく知ることができますか?唯一の方法は、両方の方法を試し、タイミングを比較することです!

時期尚早の最適化がすべての悪の根源です-Donald Knuth

最初にプロファイル、後で最適化します。

21
etheranger

コンパイラーがコンパイル時にループの正確な反復回数を予測できない場合(または少なくとも上限を予測してから、必要な回数の反復をスキップする場合)は、ループの展開は機能しません。つまり、マトリックスのサイズが可変の場合、フラグは効果がありません。

今あなたの質問に答えるために:

a)gccには、-O1、-O2などのさまざまな最適化フラグによるこの種の最適化が含まれていますか?

いいえ、コードをより高速に実行できる場合とそうでない場合があり、通常は実行可能ファイルが大きくなるため、明示的に設定する必要があります。

b)ループの展開を利用するためにコード内でプラグマを使用する必要がありますか、それともループが自動的に識別されますか?

プラグマはありません。 -funroll-loopsコンパイラーは、展開するループをヒューリスティックに決定します。強制的にアンロールしたい場合は、-funroll-all-loopsですが、通常はコードの実行が遅くなります。

c)展開によってパフォーマンスが向上する場合、このオプションがデフォルトで有効にならないのはなぜですか?

それは常にパフォーマンスを向上させません!また、すべてがパフォーマンスに関するものではありません。一部の人々は実際にはメモリが少ないため、小さな実行可能ファイルに関心があります(参照:組み込みシステム)

d)可能な限り最良の方法でプログラムをコンパイルするために推奨されるgcc最適化フラグは何ですか? (このプログラムは、単一のCPUファミリー用に最適化して実行する必要があります。これは、コードをコンパイルするマシンと同じです。実際には、march = nativeフラグと-O2フラグを使用します)

特効薬はありません。あなたは考え、テストし、見る必要があります。実際には、完璧なコンパイラーは存在できないという定理があります。

プログラムのプロファイルを作成しましたか?プロファイリングは、これらのことに非常に役立つスキルです。

ソース(ほとんど): https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html

8
Guido

あなたは問題について理論的な背景を得ており、実際の実行で何を得ているかを推測するのに十分なスペースを残しています。このオプションは、ループの実装、そのロード/ボディなど、さまざまな要因に依存するため、必ずしもパフォーマンスが向上するとは限りません。

各コードは異なります。より優れたパフォーマンスソリューションを見つけることに興味がある場合は、両方のバリアントを実行し、それらの実行時間を測定して比較することをお勧めします。

時間測定のアイデアを得るには、以下の回答で this アプローチを参照してください。つまり、コードをサイクルにラップするだけで、プログラムの実行に数秒かかることになります。ループ自体を最適化しているので、アプリを何度も実行するシェルスクリプトを作成することをお勧めします。

4