コンパイラは関数呼び出しをインライン化することがあります。つまり、呼び出された関数のコードを呼び出し側の関数に移動します。コールスタックにデータをプッシュしたり、コールスタックからポップしたりする必要がないため、これにより処理が少し速くなります。
だから私の質問は、なぜコンパイラがすべてをインライン化しないのですか?私はそれが実行可能ファイルを著しく速くすると思います。
私が考えることができる唯一の理由は、非常に大きな実行可能ファイルですが、最近数百GBのメモリを使用することは本当に重要ですか?改善されたパフォーマンスは価値がありますか?
コンパイラがすべての関数呼び出しをインライン化しない他の理由はありますか?
最初に、インラインの主な効果の1つは、呼び出しサイトでさらに最適化できることです。
あなたの質問のために:インライン化するのが難しいまたは不可能でさえあるものがあります:
動的にリンクされたライブラリ
動的に決定される関数(動的ディスパッチ、関数ポインターを通じて呼び出される)
再帰関数(末尾再帰が可能)
コードを持たない関数(ただし、リンク時の最適化により、一部の関数ではこれが可能になります)
次に、インライン化は有益な効果だけではありません。
実行ファイルが大きいほど、ディスクの場所が多くなり、読み込み時間が長くなります
実行可能ファイルが大きくなると、キャッシュプレッシャーが増加します(単純なゲッターなどの十分に小さい関数をインライン化すると、実行可能ファイルのサイズとキャッシュプレッシャーが減少する場合があります)。
そして最後に、実行するのに重要な時間を要する関数の場合、ゲインは痛みに値しません。
主な制限は、実行時のポリモーフィズムです。 foo.bar()
を書き込んだときに動的ディスパッチが発生している場合、メソッド呼び出しをインライン化することはできません。これは、コンパイラがすべてをインライン化しない理由を説明しています。
再帰呼び出しも簡単にはインライン化できません。
クロスモジュールインライン化も技術的な理由により実行が困難です(増分再コンパイルはexでは不可能です)。
ただし、コンパイラーは多くのことをインライン化します。
まず、常にインライン化できるわけではありません。再帰関数は常にインライン化できるとは限りません(ただし、fact
の再帰的な定義とfact(8)
の出力のみを含むプログラムはインライン化できます)。
その場合、インライン化は必ずしも有益ではありません。コンパイラーがインライン化を行いすぎて、結果のコードが十分に大きくなり、ホットパーツが適合しない場合。 L1命令キャッシュは、インライン化されていないバージョン(L1キャッシュに簡単に適合します)よりもはるかに遅くなる可能性があります...また、最近のプロセッサはCALL
マシン命令の実行が非常に高速です(少なくとも既知の場所、つまり直接コールであり、コールスルーポインタではありません)。
最後に、完全なインライン化にはプログラム全体の分析が必要です。これは不可能かもしれません(またはコストがかかりすぎます)。 [〜#〜] gcc [〜#〜] でコンパイルされたCまたはC++では(そして Clang/LLVM でも)、有効にする必要があります link-time最適化 (例:g++ -flto -O2
を使用してコンパイルおよびリンクすることにより)、コンパイルにかなりの時間がかかります。
驚くかもしれませんが、すべてをインライン化しても実行時間は必ずしも短縮されません。コードのサイズが大きくなると、CPUがすべてのコードを一度にキャッシュに保持することが困難になる可能性があります。コードのキャッシュミスが発生する可能性が高くなり、キャッシュミスは高額になります。インライン化される可能性のある関数が大きい場合、これはさらに悪化します。
ヘッダーファイルから「インライン」としてマークされたコードの大きなチャンクを取り出してソースコードに配置することで、時々顕著なパフォーマンスの改善がありました。これにより、コードはすべての呼び出しサイトではなく1か所だけに配置されます。次に、CPUキャッシュがより適切に使用され、コンパイル時間も向上します...
すべてをインライン化すると、ディスクメモリの消費量が増えるだけでなく、それほど多くない内部メモリの消費量も増えることになります。コードはコードセグメントのメモリにも依存していることを忘れないでください。関数が10000か所から呼び出された場合(かなり大きなプロジェクトの標準ライブラリからのものなど)、その関数のコードは10000倍多くの内部メモリを占有します。
別の理由は、JITコンパイラーかもしれません。すべてがインラインの場合、動的にコンパイルされるホットスポットはありません。
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分かかります。