C++ でインライン関数を使用する利点と欠点は何ですか? コンパイラが出力するコードのパフォーマンスのみが向上することがわかりますが、今日の最適化されたコンパイラ、高速な CPU、巨大なメモリなどを使用すると (メモリが不足し、すべてが 100KB のメモリに収まらなければならなかった 1980 年代とは異なります) 何が彼らは今日本当に利点がありますか?
14 に答える
利点
- コードを必要な場所にインライン化することで、プログラムは関数呼び出しと戻り部分に費やす時間を短縮できます。コードが大きくなっても、コードを高速化することになっています (以下を参照)。単純なアクセサーのインライン化は、効果的なインライン化の例です。
- インラインとしてマークすることで、関数定義をヘッダー ファイルに入れることができます (つまり、リンカーが文句を言うことなく、複数のコンパイル ユニットに含めることができます)。
短所
- コードが大きくなる可能性があります (つまり、重要な関数にインラインを使用する場合)。そのため、ページングが引き起こされ、コンパイラからの最適化が無効になる可能性があります。
- オブジェクト処理の内部を公開するため、カプセル化がわずかに壊れます(ただし、すべての「プライベート」メンバーも公開されます)。これは、PImpl パターンでインライン化を使用してはならないことを意味します。
- カプセル化をわずかに破ります 2: C++ インライン化はコンパイル時に解決されます。つまり、インライン関数のコードを変更する必要がある場合は、それを使用してすべてのコードを再コンパイルして、確実に更新されるようにする必要があります (同じ理由で、関数パラメーターのデフォルト値は避けます)。
- ヘッダーで使用すると、ヘッダー ファイルが大きくなるため、興味深い情報 (クラス メソッドのリストなど) が、ユーザーが気にしないコードで薄められます (これが、インライン関数をヘッダー内で宣言する理由です)。クラスに含まれますが、クラス本体の後のヘッダーで定義し、クラス本体内では決して定義しません)。
インラインマジック
- コンパイラは、インラインとしてマークした関数をインライン化する場合としない場合があります。また、コンパイル時またはリンク時にインラインとしてマークされていない関数をインライン化することもできます。
- インラインは、コンパイラによって制御されるコピー/貼り付けのように機能します。これは、プリプロセッサ マクロとはまったく異なります。マクロは強制的にインライン化され、すべての名前空間とコードが汚染され、簡単にデバッグできず、コンパイラがそれを非効率的であると判断した場合。
- クラス自体の本体内で定義されたクラスのすべてのメソッドは、「インライン化されている」と見なされます (コンパイラがインライン化しないことを決定できる場合でも)。
- 仮想メソッドはインライン化できないと想定されています。それでも、コンパイラがオブジェクトの型を確実に認識できる場合 (つまり、オブジェクトが同じ関数本体内で宣言および構築された場合) は、コンパイラがオブジェクトの型を正確に認識しているため、仮想関数でさえインライン化されます。
- テンプレートのメソッド/関数は常にインライン化されるとは限りません (ヘッダーに存在しても、自動的にインライン化されるわけではありません)。
- 「インライン」の次のステップは、テンプレート メタプログラミングです。つまり、コンパイル時にコードを「インライン化」することにより、コンパイラは関数の最終結果を推測できる場合があります...したがって、複雑なアルゴリズムを一種の
return 42 ;
ステートメントに減らすことができます。これは私にとって極端な inliningです。実際にはめったに発生しませんが、コンパイル時間が長くなり、コードが肥大化せず、コードが高速になります。しかし、聖杯のように、ほとんどの処理はこの方法で解決できないため、どこにでも適用しようとしないでください...それでも、これはとにかくクールです...
:-p
インライン関数は、パラメーターや戻りアドレスのようにスタックのオン/オフをプッシュおよびポップする必要がないため、高速です。ただし、バイナリがわずかに大きくなります。
それは大きな違いをもたらしますか?ほとんどの場合、最新のハードウェアでは十分ではありません。しかし、それは違いを生む可能性があり、一部の人にとっては十分です.
何かをインラインにマークしても、それがインラインになるという保証はありません。これは、コンパイラへの単なる提案です。仮想関数がある場合や、再帰が関係している場合など、不可能な場合があります。また、コンパイラがそれを使用しないことを選択する場合もあります。
このような状況が検出可能な違いを生んでいることがわかりました。
inline int aplusb_pow2(int a, int b) {
return (a + b)*(a + b) ;
}
for(int a = 0; a < 900000; ++a)
for(int b = 0; b < 900000; ++b)
aplusb_pow2(a, b);
古風な C および C++ では、最適化の可能性に関するコンパイラへの提案 (単なる提案にすぎません) のinline
ようなものです。register
最新の C++ では、inline
複数の定義 (宣言ではない) が異なる翻訳単位で見つかった場合、それらはすべて同じであり、リンカーは自由に 1 つを保持し、他のすべてのものを破棄できることをリンカーに伝えます。
inline
関数がヘッダー ファイルで定義されている場合 (どんなに複雑または「線形」であっても) は必須であり、リンカによって「複数の定義」エラーが発生することなく複数のソースがそれをインクルードできるようにします。
クラス内で定義されたメンバー関数は、(グローバル関数とは対照的に) テンプレート関数と同様に、既定で "インライン" です。
//fileA.h
inline void afunc()
{ std::cout << "this is afunc" << std::endl; }
//file1.cpp
#include "fileA.h"
void acall()
{ afunc(); }
//main.cpp
#include "fileA.h"
void acall();
int main()
{
afunc();
acall();
}
//output
this is afunc
this is afunc
fileA.h が 2 つの .cpp ファイルに含まれていることに注意してくださいafunc()
。リンカーはそれらの 1 つを破棄します。noinline
が指定されている場合、リンカは文句を言います。
インライン化は、無視してもよいコンパイラへの提案です。小さなコードに最適です。
関数がインライン化されている場合、実際に別の関数を呼び出すのではなく、基本的に関数呼び出しが行われるコードに挿入されます。実際の呼び出しを行う必要がないため、これにより速度が向上します。
また、呼び出しによって発生した新しい命令でパイプラインをリロードする必要がないため、パイプライン処理で CPU を支援します。
唯一の欠点は、バイナリ サイズが大きくなる可能性があることですが、関数が小さい限り、これはあまり問題にはなりません。
私は最近、この種の決定をコンパイラに任せる傾向があります (まあ、いずれにせよ賢いものです)。それらを書いた人々は、基礎となるアーキテクチャについてはるかに詳細な知識を持っている傾向があります。
インライン関数は、コンパイラで使用される最適化手法です。関数プロトタイプの先頭に inline キーワードを追加するだけで、関数をインラインにすることができます。インライン関数は、その関数がコードで使用された場所に関数の完全な本体を挿入するようにコンパイラに指示します。
関数呼び出しのオーバーヘッドは必要ありません。
また、関数呼び出し中のスタック上の変数のプッシュ/ポップのオーバーヘッドも節約できます。
また、関数からの戻り呼び出しのオーバーヘッドも節約できます。
命令キャッシュを活用することで参照の局所性を高めます。
インライン化後、指定されている場合、コンパイラはプロシージャ内最適化も適用できます。これは最も重要なものです。このようにして、コンパイラはデッド コードの削除に集中できるようになり、分岐予測、帰納変数の削除などにより多くのストレスを与えることができます。
詳細を確認するには、このリンクをたどることができます http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html
共有ライブラリを構築する場合、インライン関数が重要であることを付け加えたいと思います。関数をインラインにマークしないと、バイナリ形式でライブラリにエクスポートされます。エクスポートすると、シンボル テーブルにも表示されます。一方、インライン化された関数はエクスポートされず、ライブラリ バイナリにもシンボル テーブルにもエクスポートされません。
ライブラリが実行時にロードされることを意図している場合、これは重要な場合があります。また、バイナリ互換対応ライブラリにもヒットする可能性があります。そのような場合、インラインを使用しないでください。
inline
#include
1つの定義規則に違反することなく、関数定義をヘッダーファイルに配置し、そのヘッダーファイルを複数のソースファイルに配置できます。
最適化中、多くのコンパイラは、マークを付けていなくても関数をインライン化します。通常、関数をインラインとしてマークする必要があるのは、コンパイラが知らないことを知っている場合のみです。通常、コンパイラ自体が正しい決定を下すことができるからです。
パフォーマンスがすべてではありません。C++ と C の両方が組み込みプログラミングに使用され、ハードウェアの上に置かれます。たとえば、割り込みハンドラを作成する場合は、追加のレジスタやメモリ ページがスワップされることなく、コードを一度に実行できることを確認する必要があります。そんな時に役立つのがインラインです。優れたコンパイラは、速度が必要な場合に「インライン化」を行いますが、「インライン化」はそれらを強制します。
一般的に言えば、最近のコンパイラでは、何かをインライン化することを心配するのはほとんど時間の無駄です。コンパイラは、コードを独自に分析し、コンパイラに渡された最適化フラグを指定することで、これらすべての考慮事項を実際に最適化する必要があります。速度が気になる場合は、速度を最適化するようにコンパイラに指示します。スペースが気になる場合は、コンパイラーにスペースを最適化するように指示してください。ほのめかされた別の答えとして、まともなコンパイラは、それが本当に意味がある場合、自動的にインライン化します。
また、他の人が述べているように、インラインを使用しても、インラインで何かが保証されるわけではありません。それを保証したい場合は、インライン関数の代わりにマクロを定義して実行する必要があります。
インクルードを強制するためにマクロをインライン化および/または定義するのはいつですか? - アプリケーションの全体的なパフォーマンスに影響を与えることが知られているコードのクリティカル セクションの速度が、実証され、必要に応じて向上した場合のみ。
デフォルトですべての関数をインライン化しないのはなぜですか? それはエンジニアリングのトレードオフだからです。「最適化」には、少なくとも 2 つのタイプがあります。プログラムの高速化と、プログラムのサイズ (メモリ フットプリント) の削減です。インライン化すると、一般的に高速化されます。関数呼び出しのオーバーヘッドを取り除き、スタックからパラメーターをプッシュしてプルすることを回避します。ただし、すべての関数呼び出しを関数の完全なコードに置き換える必要があるため、プログラムのメモリ フットプリントも大きくなります。さらに複雑なことに、CPU は頻繁に使用されるメモリのチャンクを CPU 上のキャッシュに格納して、超高速アクセスを実現していることを思い出してください。プログラムのメモリ イメージを十分に大きくすると、プログラムはキャッシュを効率的に使用できなくなり、最悪の場合、インライン化によって実際にプログラムの速度が低下する可能性があります。
私たちのコンピューター サイエンスの教授は、C++ プログラムでインラインを使用しないように私たちに強く勧めました。理由を尋ねられたとき、彼は親切に、最新のコンパイラーはインラインを使用するタイミングを自動的に検出する必要があると説明してくれました。
そうです、インラインは可能な限り使用される最適化手法になる可能性がありますが、とにかく関数をインライン化できる場合はいつでも、これはすでに行われているようです。
ここでの別の議論からの結論:
インライン関数に欠点はありますか?
どうやら、インライン関数を使用しても問題はありません。
ただし、次の点に注意してください。
インライン展開を使いすぎると、実際にはプログラムが遅くなる可能性があります。関数のサイズによっては、インライン化によってコード サイズが増減する場合があります。非常に小さなアクセサー関数をインライン化すると、通常はコード サイズが減少しますが、非常に大きな関数をインライン化すると、コード サイズが劇的に増加する可能性があります。最新のプロセッサでは、通常、命令キャッシュがより適切に使用されるため、小さなコードはより高速に実行されます。- Google ガイドライン
インライン関数の速度の利点は、関数のサイズが大きくなるにつれて減少する傾向があります。ある時点で、関数本体の実行に比べて関数呼び出しのオーバーヘッドが小さくなり、メリットが失われます- Source
インライン関数が機能しない状況がいくつかあります。
- 値を返す関数の場合。return ステートメントが存在する場合。
- 値を返さない関数の場合。ループ、switch、または goto ステートメントが存在する場合。
- 関数が再帰的である場合。-ソース
この
__inline
キーワードにより、optimize オプションを指定した場合にのみ、関数がインライン化されます。最適化が指定されている場合、それが受け入れられるかどうか__inline
は、インライン オプティマイザ オプションの設定に依存します。デフォルトでは、オプティマイザが実行されるたびにインライン オプションが有効になります。__inline
optimize を指定する場合、キーワードを無視するには、 noinline オプションも指定する必要があります。-ソース