インラインはコンパイラへのヒントまたは要求であり、関数呼び出しのオーバーヘッドを回避するために使用されることを知っています。
それでは、関数がインライン化の候補であるかどうかをどのような基準で判断できますか?どの場合、インライン化を避けるべきですか?
関数呼び出しのコストを回避することは、話の半分にすぎません。
行う:
#define
の代わりにinline
を使用しますinline
の良い候補です:より高速なコードとより小さな実行可能ファイル(コードキャッシュに残る可能性が高くなります)しないでください:
ライブラリを開発するときに、将来クラスを拡張可能にするために、次のことを行う必要があります。
inline
キーワードはコンパイラーへのヒントであることに注意してください。コンパイラーは関数をインライン化しないことを決定でき、最初にinline
とマークされていない関数をインライン化することを決定できます。通常、関数inline
のマーキングは避けます(非常に小さな関数を作成する場合は別として)。
パフォーマンスについては、賢明なアプローチは(いつものように)アプリケーションのプロファイルを作成し、最終的にinline
ボトルネックを表す関数のセットです。
参照:
編集:Bjarne Stroustrup、C++プログラミング言語:
関数は
inline
と定義できます。例えば:
inline int fac(int n)
{
return (n < 2) ? 1 : n * fac(n-1);
}
inline
指定子は、関数のコードを一度設定してから通常の関数呼び出しメカニズムを介して呼び出すのではなく、fac()
の呼び出しに対してコードを生成しようとするコンパイラーへのヒントです。賢いコンパイラーは、呼び出しfac(6)
に対して定数720
を生成できます。相互に再帰的なインライン関数、入力に依存する、または依存しないインライン関数などの可能性により、inline
関数のすべての呼び出しが実際にインライン化されることを保証できなくなります。コンパイラーの賢さの程度を規定することはできないため、あるコンパイラーが720
、別の6 * fac(5)
、さらに別のインライン化されていない呼び出しfac(6)
を生成する場合があります。異常に巧妙なコンパイルおよびリンク機能がない場合にインライン化を可能にするには、インライン関数の定義(宣言だけでなく)がスコープ内になければなりません(§9.2)。
inline
指定子は、関数のセマンティクスに影響しません。特に、インライン関数にはまだ一意のアドレスがあり、インライン関数のstatic
変数(§7.1.2)もあります。
EDIT2:ISO-IEC 14882-1998、7.1.2関数指定子
inline
指定子を持つ関数宣言(8.3.5、9.3、11.4)は、インライン関数を宣言します。インライン指定子は、呼び出しポイントでの関数本体のインライン置換が通常の関数呼び出しメカニズムより優先されることを実装に示します。呼び出しポイントでこのインライン置換を実行するための実装は必要ありません。ただし、このインライン置換が省略された場合でも、7.1.2で定義されているインライン関数の他の規則は引き続き尊重されます。
inline
は最適化とはほとんど関係ありません。 inline
は、定義された関数がプログラム内で複数回発生した場合にエラーを生成しないようにするコンパイラーへの命令であり、使用されるすべての翻訳で出現し、出現するすべての場所で定義が発生するという約束ですまったく同じ定義。
上記の規則を考えると、inline
は、宣言だけで必要なものに対する追加の依存関係を含める必要のない短い関数に適しています。定義に遭遇するたびに、それを解析し、その本体のコードを生成する必要があるため、単一のソースファイルで一度だけ定義された関数のコンパイラオーバーヘッドがいくらか生じます。
コンパイラは、選択した関数呼び出しをインライン(つまり、関数の呼び出しをその関数のアクションを実行するコードに置き換える)することができます。以前は、呼び出しと同じ変換単位で宣言されていなかった関数を「明らかに」インライン化できなかったが、リンク時間の最適化の使用が増えているため、現在はそうではありません。 inline
とマークされた関数はインライン化できない可能性があるという事実も同様です。
関数をインライン化するようコンパイラーに指示することは最適化であり、最適化の最も重要なルールは、早すぎる最適化がすべての悪の根源であるということです。常に明確なコードを(効率的なアルゴリズムを使用して)作成し、プログラムのプロファイルを作成して、時間がかかりすぎている機能のみを最適化します。
特定の関数が非常に短く単純であり、タイトな内部ループで何万回も呼び出される場合、それは良い候補です。
ただし、驚くかもしれません-多くのC++コンパイラーが小さな関数を自動的にインライン化します-また、インラインへの要求も無視します。
見つけるための最良の方法は、プログラムをプロファイルし、何度も呼び出される小さな関数をマークし、inline
としてCPUサイクルを焼き切ることです。ここでのキーワードは「小さい」です-関数呼び出しのオーバーヘッドが関数で費やされた時間と比較して無視できる場合、それらをインライン化しても意味がありません。
私がお勧めする他の使用法は、キャッシュミスを関連させるのに十分な頻度でパフォーマンスクリティカルなコードで呼び出される小さな関数がある場合、おそらく同様にインライン化する必要があるということです。繰り返しますが、それはプロファイラーがあなたに伝えることができるはずのものです。
早すぎる最適化はすべての悪の根源です!
経験則として、私は通常「getter」と「setter」のみをインライン化します。コードが機能し、安定したら、プロファイリングにより、インライン化の恩恵を受ける可能性がある関数を表示できます。
一方、最新のコンパイラのほとんどは非常に優れた最適化アルゴリズムを備えており、インライン化すべきものをインライン化します。
再評価-インラインのワンライナー関数を記述し、後で他の関数を心配します。
インライン関数mightは、引数をスタックにプッシュする必要をなくすことにより、コードのパフォーマンスを改善します。問題の関数がコードの重要な部分にある場合、プロジェクトの最適化部分でインラインではなくインラインの決定を行う必要があります。
c ++ faq のインラインの詳細を読むことができます。
インライン関数は最適化としてではなく、コードを読みやすくするためによく使用します。コード自体は、コメントや説明的な名前などよりも短く、理解しやすい場合があります。次に例を示します。
void IncreaseCount() { freeInstancesCnt++; }
読者はすぐにコードの完全なセマンティクスを知っています。
私は通常、3〜4個の単純なステートメントをインラインで使用して関数を作成するという経験則に従います。しかし、それはコンパイラへの単なるヒントであることを覚えておくのは良いことです。インラインにするかしないかの最後の呼び出しは、コンパイラーのみによって行われます。これらの多くのステートメントがある場合、愚かなコンパイラのようにインラインで宣言しません。コードが膨張する可能性があります。
最良の方法は、インライン化されたものとインライン化されていないものについて、生成された命令を調べて比較することです。ただし、inline
を省略することは常に安全です。 inline
を使用すると、望ましくないトラブルが発生する可能性があります。
インラインを使用するかどうかを決定するとき、私は通常、次の考えに留意します。現代のマシンでは、メモリレイテンシは生の計算よりも大きなボトルネックになる可能性があります。頻繁に呼び出されるインライン関数は、実行可能ファイルのサイズを大きくすることが知られています。さらに、そのような関数はCPUのコードキャッシュに格納され、そのコードにアクセスする必要がある場合にキャッシュミスの数を減らします。
したがって、あなたは自分で決定する必要があります:インライン化は、生成されたマシンコードのサイズを増加または減少させますか?関数を呼び出すとキャッシュミスが発生する可能性はどのくらいですか?コード全体に散らばっている場合、その可能性は高いと言えます。単一のタイトループに制限されている場合、可能性は低いと考えられます。
私は通常、以下のリストの場合にインライン化を使用します。ただし、パフォーマンスを本当に懸念している場合は、プロファイリングが不可欠です。さらに、コンパイラーが実際にヒントを取るかどうかを確認することもできます。
関数コードが小さい場合にのみinline関数修飾子を使用する必要があります。関数が大きい場合は、メモリ領域の節約は実行速度を比較的小さく犠牲にするだけの価値があるため、通常の関数を選択する必要があります。
また、大規模プロジェクトを保守する場合、インライン方式には重大な副作用があります。インラインコードが変更されると、それを使用するすべてのファイルはコンパイラーによって自動的に再構築されます(これは優れたコンパイラーです)。これは開発時間の多くを浪費する可能性があります。
inline
メソッドがソースファイルに転送され、インライン化されなくなった場合、プロジェクト全体を再構築する必要があります(少なくともこれは私の経験です)。また、メソッドがインラインに変換されるとき。
コードがインラインとして使用するのに十分小さいと思い、インライン関数がコードを複製し、関数が呼び出されたときに貼り付けることを覚えているので、実行時間を増やすのに十分かもしれませんが、メモリ消費も増加します。 loop/static変数/ recursive/switch/goto/Virtual関数を使用している場合、インライン関数は使用できません。仮想とは、コンパイル時にランタイムとインラインが同時に使用できないようにするための手段です。