コンパイラがすべてをインライン化しないのはなぜですか?[閉まっている]


12

コンパイラはインライン関数呼び出しを行う場合があります。つまり、呼び出された関数のコードを呼び出し元の関数に移動します。これにより、コールスタックのオン/オフを切り替える必要がないため、処理が少し速くなります。

だから私の質問は、なぜコンパイラはすべてをインライン化しないのですか?実行可能ファイルが著しく高速になると思います。

私が考えることができる唯一の理由は、実行可能ファイルが非常に大きいことですが、最近では何百GBものメモリが必要なのでしょうか?パフォーマンスの向上はそれだけの価値はありませんか?

コンパイラがすべての関数呼び出しをインライン化しない他の理由はありますか?


17
あなたについてのIDK、しかし私はただ横になっている何百GBものメモリを持っていません。
アンプト14

2
Isn't the improved performance worth it?ループを100回実行し、いくつかの深刻な数値を処理するメソッドの場合、2つまたは3つの引数をCPUレジスタに移動するオーバーヘッドはありません。
ドーバル14

5
あなたは過度に一般的です、「コンパイラ」は「すべてのコンパイラ」を意味し、「すべて」は本当に「すべて」を意味しますか?その場合、答えは簡単です。単にインライン化できない状況があります。再帰が思い浮かびます。
オタビオデシオ14

17
キャッシュの局所性は、小さな関数呼び出しのオーバーヘッドよりもはるかに重要な方法です。
SKロジック14

3
最近、何百GFLOPSの処理能力でパフォーマンスの改善が本当に重要ですか?
ムービシエル14

回答:


22

インラインの主な効果の1つは、呼び出しサイトでさらに最適化できることです。

あなたの質問:インライン化するのが難しい、あるいは不可能なこともあります:

  • 動的にリンクされたライブラリ

  • 動的に決定された関数(関数ポインターを通じて呼び出される動的ディスパッチ)

  • 再帰関数(末尾再帰が可能)

  • コードを持たない関数(ただし、リンク時間の最適化により、一部のコードでこれが可能になります)

次に、インライン化は有益な効果だけでなく:

  • より大きな実行可能ファイルは、より多くのディスク場所とより大きなロード時間を意味します

  • 実行可能ファイルが大きくなると、キャッシュのプレッシャーが増加します(単純なゲッターなどの十分に小さい関数をインライン化すると、実行可能ファイルのサイズとキャッシュのプレッシャーが減少する可能性があります)

そして最後に、実行するのに重要な時間を要する関数の場合、ゲインは痛みに見合うだけのものではありません。


3
一部の再帰呼び出しはインライン化(​​テール呼び出し)できますが、オプションで明示的なスタックを追加すると、すべてを反復に変換できます
ラチェットフリーク14

@ratchetfreak、非テール再帰呼び出しをテールテールに変換することもできます。しかし、それは私にとっては「難しい」領域(特に、再帰関数がある場合、または戻り値をシミュレートするためにジャンプする場所を動的に決定する必要がある場合)ですが、それは不可能ではありません(継続フレームワークを配置し、存在することを考慮すると、より簡単になります)。
AProgrammer

11

主な制限は、ランタイムポリモーフィズムです。書き込み時に動的なディスパッチが発生する場合foo.bar()、メソッド呼び出しをインライン化することはできません。これは、コンパイラがすべてをインライン化しない理由を説明しています。

再帰呼び出しも簡単にインライン化できません。

技術的な理由により、モジュール間のインライン展開も実行が困難です(exでは、インクリメンタルな再コンパイルは不可能です)

ただし、コンパイラーは多くのことをインライン化します。


3
仮想ディスパッチによるインライン化は非常に困難ですが、不可能ではありません。一部のC ++コンパイラは、特定の状況下でそれを実行できます。
bstamour

2
...および一部のJITコンパイラ(開発化)。
フランク14

@bstamour適切な最適化を備えた言語の半分まともなコンパイラは、コンパイル時に動的型が認識可能なオブジェクトの宣言仮想メソッドの呼び出しを静的にディスパッチ、つまり仮想化します。これにより、(または別の)インライン化フェーズの前に仮想化フェーズが発生する場合、インライン化が容易になります。しかし、これは簡単です。他に何か意味がありましたか?実際の「仮想ディスパッチによるインライン化」をどのように実現できるかわかりません。すなわちdevirtualise - -インラインするためには、静的な型を知っている必要がありますインライン化手段の存在があってません何の仮想派遣
underscore_d

9

まず、常にインラインであるとは限りません。たとえば、再帰関数は常にインライン化できない場合があります(ただし、factの印刷だけでの再帰定義を含むプログラムfact(8)はインライン化できます)。

次に、インライン化が常に有益であるとは限りません。コンパイラーがインライン化して結果コードが十分に大きくなり、ホット部分がL1命令キャッシュなどに収まらない場合、インライン化されていないバージョン(L1キャッシュに簡単に収まる)よりもかなり遅くなる可能性があります。また、最近のプロセッサはCALLマシン命令の実行が非常に高速です(少なくとも既知の場所、つまり、ポインタを介した呼び出しではなく、直接呼び出し)。

最後に、完全なインライン化にはプログラム全体の分析が必要です。これは不可能な場合があります(またはコストがかかりすぎます)。GCCによってコンパイルされたCまたはC ++ (およびClang / LLVMを使用)では、リンク時間の最適化を有効にする必要があります(たとえば、でコンパイルおよびリンクしますg++ -flto -O2)。これにはかなりのコンパイル時間がかかります。


1
記録のために、LLVM / Clang(および他のいくつかのコンパイラ)もリンク時最適化をサポートしています。
あなたは14

そんなこと知ってる; LTOは前世紀に存在していました(少なくとも一部のMIPS独自のコンパイラではIIRC)。
バジルStarynkevitch 14

7

驚くかもしれませんが、すべてをインライン化しても実行時間は必ずしも短縮されません。コードのサイズが大きくなると、CPUがすべてのコードを一度にキャッシュに保持するのが難しくなります。コードのキャッシュミスが発生する可能性が高くなり、キャッシュミスは高価になります。インライン化される可能性のある関数が大きい場合、これはさらに悪化します。

ヘッダーファイルから「インライン」とマークされた大量のコードを取り出してソースコードに入れることにより、パフォーマンスが大幅に改善されることがありました。そのため、コードはすべての呼び出しサイトではなく1か所にしかありません。その後、CPUキャッシュがより適切に使用され、コンパイル時間も短縮されます...


これは単に繰り返しポイントがで作られたと説明しているようだ前に解答時間前に投稿されました
ブヨ

1
キャッシュは何ですか?L1?L2?L3?どちらが重要ですか?
ピーターモーテンセン14

1

すべてをインライン化すると、ディスクメモリの消費量が増加するだけでなく、内部メモリの消費量も増加します。コードはコードセグメントのメモリにも依存していることに注意してください。関数が10000の場所(かなり大きなプロジェクトの標準ライブラリからのものなど)から呼び出されると、その関数のコードは10000倍の内部メモリを占有します。

もう1つの理由は、JITコンパイラーかもしれません。すべてがインラインの場合、動的にコンパイルされるホットスポットはありません。


1

1つは、すべてをインライン化すると非常に悪い結果になる単純な例があります。この単純なCコードを考えてみましょう。

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

すべてがインライン化されるとどうなるかを推測します。

次に、インライン化により処理が高速になると仮定します。時々そうですが、常にそうではありません。理由の1つは、命令キャッシュに収まるコードがはるかに高速に実行されることです。10か所から関数を呼び出すと、常に命令キャッシュにあるコードを実行します。インライン化されている場合、コピーはいたるところにあり、実行速度が大幅に低下します。

他にも問題があります:インライン化は巨大な関数を生成します。巨大な関数を最適化するのははるかに困難です。関数を別のファイルに隠して、コンパイラーがそれらをインライン化しないようにすることで、パフォーマンスが重要なコードを大幅に向上させました。その結果、これらの関数用に生成されたコードは、非表示になったときの方がはるかに優れていました。

ところで。「数百GBのメモリ」はありません。私の職場のコンピューターには「数百GBのハードドライブ領域」さえありません。また、アプリケーションが「数百GBのメモリ」である場合、アプリケーションをメモリにロードするだけで20分かかります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.