12

gcc は-O2フラグを渡すとコードを最適化しますが、すべてのソース ファイルをオブジェクト ファイルにコンパイルしてからリンクすると、実際にどれだけうまく機能するのか疑問に思っています。

次に例を示します。

// in a.h
int foo(int n);

// in foo.cpp
int foo(int n) {
  return n;
}

// in main.cpp
#include "a.h"
int main(void) {
  return foo(5);
}

// code used to compile it all
gcc -c -O2 foo.cpp -o foo.o
gcc -c -O2 main.cpp -o main.o
gcc -O2 foo.o main.o -o executable

通常、gcc はfoo小さな関数であり、 を-O2有効-finline-small-functionsにするため、インライン化する必要がありますよね? しかしここでは、gcc はオブジェクト ファイルを作成する前に and のコードのみfoomain独立して参照するため、そのような最適化は行われませんよね? では、このようなコンパイルは本当にコードを遅くするのでしょうか?

ただし、次のようにコンパイルすることもできます。

gcc -O2 foo.cpp main.cpp -o executable

その方が速いでしょうか?そうでない場合、この方法の方が高速ですか?

// in foo.cpp
int foo(int n) {
  return n;
}

// in main.cpp
#include "foo.cpp"
int main(void) {
  return foo(5);
}

編集: を調べたところobjdump、その逆アセンブルされたコードは、機能する#include "foo.cpp"ものだけを示していました。

4

3 に答える 3

9

C と C++ が使用する別のコンパイル モデルに関する問題をご自身で再発見されたようです。確かにメモリー要件は緩和されますが (これは作成時に重要でした)、最小限の情報のみをコンパイラーに公開することで実現しています。つまり、一部の最適化 (このようなもの) は実行できません。

モジュールシステムを備えた新しい言語は、必要なだけ多くの情報を公開できます。モジュールがC ++の次のバージョンに移行した場合、これらの利点が失われることを期待できます...

それまでの間、最も簡単なのはリンク時の最適化と呼ばれるものです。各 TU (翻訳単位) で可能な限り多くの最適化を実行してオブジェクト ファイルを取得するという考え方ですが、従来のオブジェクト ファイル (アセンブリを含む) を IR (コンパイラが最適化するために使用する中間表現) で強化することもできます。 ) 関数の一部またはすべて。

これらのオブジェクト ファイルをマージするためにリンカーが呼び出されると、単にファイルをマージするのではなく、IR 表現をマージし、多数の最適化パス (定数伝播、インライン化など) を再実行してから、アセンブリを作成します。自分の。これは、単なるリンカーではなく、実際にはバックエンド オプティマイザーであることを意味します。

もちろん、すべての最適化パスと同様に、これにはコストがかかるため、コンパイルに時間がかかります。また、コンパイラリンカーの両方に、この動作をトリガーする特別なオプションを渡す必要があることを意味します。gcc の場合は、-ltoまたは-O4.

于 2012-04-21T15:57:46.313 に答える
7

プログラム全体の最適化とも呼ばれるリンク時最適化(LTO)を探しているかもしれません。

于 2012-04-21T14:18:36.940 に答える
1

GCC を使用しているため、C99inline関数指定子メカニズムを使用できます。これは ISO/IEC 9899:1999 か​​らのものです。

§ 6.7.4 関数指定子

構文

¶1関数指定子:

      inline

制約

¶2 関数指定子は、関数の識別子の宣言でのみ使用されます。

¶3 外部リンケージを持つ関数のインライン定義には、静的保存期間を持つ変更可能なオブジェクトの定義が含まれてはならず、内部リンケージを持つ識別子への参照が含まれてはなりません。

¶4 ホスト環境では、inline関数指定子は の宣言に現れてはならないmain

セマンティクス

¶5inline関数指定子で宣言された関数はインライン関数です。関数指定子は複数回出現する場合があります。動作は、一度だけ表示された場合と同じです。関数をインライン関数にすることは、関数の呼び出しが可能な限り高速であることを示唆しています。118)そのような提案が有効である範囲は、実装によって定義されます。119)

¶6 内部リンケージを持つ関数はすべてインライン関数にすることができます。外部リンケージを持つ関数の場合、次の制限が適用されます。関数がinline 関数指定子で宣言されている場合、同じ翻訳単位でも定義される必要があります。翻訳単位内の関数のすべてのファイル スコープ宣言にinlineなしの関数指定子が含まれている場合extern、その翻訳単位内の定義はインライン定義です。. インライン定義は関数の外部定義を提供せず、別の翻訳単位での外部定義を禁止しません。インライン定義は、翻訳者が同じ翻訳単位内の関数への呼び出しを実装するために使用できる外部定義の代替手段を提供します。関数の呼び出しがインライン定義を使用するか、外部定義を使用するかは指定されていません。

¶7 例 外部リンケージを持つインライン関数の宣言は、外部定義、または翻訳単位内でのみ使用可能な定義のいずれかになります。を使用したファイル スコープ宣言 externは、外部定義を作成します。次の例は、翻訳単位全体を示しています。

inline double fahr(double t)
{
    return (9.0 * t) / 5.0 + 32.0;
}
inline double cels(double t)
{
    return (5.0 * (t - 32.0)) / 9.0;
}
extern double fahr(double); // creates an external definition
double convert(int is_fahr, double temp)
{
    /* A translator may perform inline substitutions */
    return is_fahr ? cels(temp) : fahr(temp);
}

¶8 の定義はexternでも宣言されてfahrいるので外部定義ですが、セルの定義はインライン定義であることに注意してください。fahr外部リンケージがあり、参照されるためcels、外部定義は別の翻訳単位に表示される必要があります (6.9 を参照)。インライン定義と外部定義は別個のものであり、どちらも呼び出しに使用できます。

118)たとえば、「インライン置換」など、通常の関数呼び出しメカニズムの代替手段を使用する。インライン置換はテキスト置換ではなく、新しい関数も作成しません。したがって、たとえば、関数の本体内で使用されるマクロの展開では、関数が呼び出される場所ではなく、関数の本体が表示される時点での定義が使用されます。識別子は、本体が発生するスコープ内の宣言を参照します。同様に、関数は、外部定義に加えて発生するインライン定義の数に関係なく、1 つのアドレスを持ちます。

119)たとえば、実装はインライン置換をまったく実行しないか、inline宣言のスコープ内の呼び出しに対してインライン置換のみを実行する可能性があります。

120)インライン定義は、対応する外部定義および他の翻訳単位の他の対応するインライン定義とは異なるため、静的ストレージ期間を持つすべての対応するオブジェクトも、それぞれの定義で異なります。


標準化される前に、GCC にもinlineC の関数があったことに注意してください。その表記が必要な場合は、詳細について GCC マニュアルを参照してください。

于 2012-04-21T19:47:56.203 に答える