4

おそらく誰もが何らかの最適化スイッチを使用しています (gcc の場合、最も一般的なのは-O2だと思います)。

しかし、そのようなオプションが存在する場合、gcc (および VS や Clang などの他のコンパイラ)は実際に何をするのでしょうか?

もちろん、プラットフォームやコンパイラのバージョンなどに大きく依存するため、明確な答えはありません。しかし、可能であれば、一連の「経験則」を収集したいと思います。コードを高速化するためのいくつかのトリックについて考える必要があるのはいつですか? また、その仕事をコンパイラに任せるべきなのはいつですか?

たとえば、さまざまな最適化レベルで、コンパイラはそのような (少し人為的な...) ケースでどこまで行くでしょうか:

1) sin(3.141592) // コンパイル時に評価されますか、それとも計算を高速化するためにルックアップ テーブルを考えるべきですか?

2) int a = 0; a = exp(18), cos(1.57), 2; // 式の値が 2 に等しいため、必要ではありませんが、コンパイラは exp と cos を評価しますか?

3)

for (size_t i = 0; i < 10; ++i) {
  int a = 10 + i;
}

// 目に見える副作用がないため、コンパイラはループ全体をスキップしますか?

他の例を思いつくかもしれません。

4

3 に答える 3

6

コンパイラの機能を知りたい場合は、コンパイラのドキュメントを参照することをお勧めします。最適化については、たとえばLLVM の分析パスと変換パスを調べることができます。

1) sin(3.141592) // コンパイル時に評価されますか?

おそらく。IEEE float 計算には非常に正確なセマンティクスがあります。ちなみに、実行時にプロセッサ フラグを変更すると、これは驚くべきことかもしれません。

2) int a = 0; a = exp(18), cos(1.57), 2;

場合によります:

  • 関数expcosがインラインかどうか
  • そうでない場合は、正しく注釈が付けられているかどうか (したがって、コンパイラは副作用がないことを認識します)

C または C++ 標準ライブラリから取得した関数については、正しく認識/注釈を付ける必要があります。

計算の省略については、次のとおりです。

  • -adce: 積極的なデッドコードの排除
  • -dce: デッドコードの除去
  • -die: デッド命令の削除
  • -dse: デッドストア消去

コンパイラは役に立たないコードを見つけるのが大好きです:)

3)

2)実際に似ています。ストアの結果は使用されず、副作用はありません。

  • -loop-deletion: デッドループを削除

最後に、コンパイラーをテストしてみませんか?

#include <math.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  double d = sin(3.141592);
  printf("%f", d);

  int a = 0; a = (exp(18), cos(1.57), 2); /* need parentheses here */
  printf("%d", a);

  for (size_t i = 0; i < 10; ++i) {
    int a = 10 + i;
  }

  return 0;
}

Clang は、コンパイル中にすでに役立つように努めています。

12814_0.c:8:28: warning: expression result unused [-Wunused-value]
  int a = 0; a = (exp(18), cos(1.57), 2);
                           ^~~ ~~~~
12814_0.c:12:9: warning: unused variable 'a' [-Wunused-variable]
    int a = 10 + i;
        ^

発行されたコード (LLVM IR):

@.str = private unnamed_addr constant [3 x i8] c"%f\00", align 1
@.str1 = private unnamed_addr constant [3 x i8] c"%d\00", align 1

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind uwtable {
  %1 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str, i64 0, i64 0), double 0x3EA5EE4B2791A46F) nounwind
  %2 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str1, i64 0, i64 0), i32 2) nounwind
  ret i32 0
}

私たちは次のように述べています。

  • 予測どおり、sin計算はコンパイル時に解決されました
  • 予想通り、exp完全cosに剥ぎ取られました。
  • 予測どおり、ループも削除されました。

コンパイラの最適化についてさらに詳しく知りたい場合は、次のことをお勧めします。

  • IR の読み方を学ぶ (信じられないほど簡単で、アセンブリのほうがはるかに簡単です)
  • LLVM Try Out ページを使用して、仮定をテストします
于 2012-09-07T11:27:44.670 に答える
1

コンパイラには多数の最適化パスがあります。すべての最適化パスは、多数の小さな最適化を担当します。たとえば、コンパイル時に算術式を計算するパスがあるとします (たとえば、5MB を 5 * (1024*1024) としてペナルティなしで表現できるようにするため)。別のパスは関数をインライン化します。もう 1 つは、到達不能なコードを検索し、それを強制終了します。等々。

次に、コンパイラの開発者は、これらのパスのどれをどの順序で実行するかを決定します。たとえば、次のコードがあるとします。

int foo(int a, int b) {
  return a + b;
}

void bar() {
  if (foo(1, 2) > 5)
    std::cout << "foo is large\n";
}

これに対してデッドコードの削除を実行しても、何も起こりません。同様に、式の削減を実行しても何も起こりません。しかし、インライナーは foo がインライン化するのに十分小さいと判断する可能性があるため、引数を置き換えて、 bar 内の呼び出しを関数本体に置き換えます。

void bar() {
  if (1 + 2 > 5)
    std::cout << "foo is large\n";
}

ここで式削減を実行すると、最初に 1 + 2 が 3 であると判断され、次に 3 > 5 が false であると判断されます。したがって、次のようになります。

void bar() {
  if (false)
    std::cout << "foo is large\n";
}

そして、デッドコードの除去は if(false) を見てそれを殺すので、結果は次のようになります:

void bar() {
}

しかし、以前は大きくて複雑だったバーが突然非常に小さくなりました。したがって、インライナーを再度実行すると、バーを呼び出し元にインライン化できます。これにより、さらに多くの最適化の機会などが明らかになる可能性があります。

コンパイラ開発者にとって、これはコンパイル時間と生成されたコードの品質の間のトレードオフです。彼らは、ヒューリスティック、テスト、および経験に基づいて、実行する一連のオプティマイザーを決定します。しかし、1 つのサイズがすべての人に合うわけではないため、これを微調整するためにいくつかのノブを公開しています。gcc と clang の主な問題は、-O オプション ファミリです。-O1 オプティマイザーの短いリストを実行します。-O3 は、より高価なオプティマイザーを含む非常に長いリストを実行し、より頻繁にパスを繰り返します。

どのオプティマイザーを実行するかを決定する以外に、オプションは、さまざまなパスで使用される内部ヒューリスティックを微調整することもできます。たとえば、インライン化には通常、関数をインライン化する価値があるかどうかを決定する多くのパラメーターがあります。-O3 を渡すと、これらのパラメーターは、パフォーマンスが向上する可能性がある場合はいつでも関数をインライン化する傾向があります。-Os を渡すと、パラメーターによって、非常に小さな関数 (または確実に 1 回だけ呼び出される関数) のみがインライン化されます。

于 2012-09-07T12:13:24.523 に答える
0

コンパイラは、思いもよらないあらゆる種類の最適化を行います。特に C++ コンパイラ。

ループの展開、関数のインライン化、デッド コードの削除、複数の命令を 1 つの命令に置き換えるなどの処理を行います。

私からできるアドバイスは次のとおりです。C/C++ コンパイラでは、多くの最適化が実行されると確信できます。

[1]を見てください。

[1] http://en.wikipedia.org/wiki/Compiler_optimization

于 2012-09-07T11:12:57.650 に答える