インライン関数を使用する場合と使用しない場合


185

インラインはコンパイラへのヒントまたは要求であり、関数呼び出しのオーバーヘッドを回避するために使用されることは知っています。

それでは、関数がインライン化の候補であるかどうかをどのような根拠で判断できますか?どの場合、インライン化を避けるべきですか?


11
inlineCFLAGSGentooの初心者-O3 -funroll-loops -finline-functions
にとって

1
インラインを使用しない理由は、一部のデバッガーでは、ブレークポイントを設定したり、インライン関数にステップインしたりできないためです。
Rob deFriesse、2009


5
関数をインライン化する必要があるかどうかを判断するべきではありません。コンパイラーに任せてください。それはあなたよりも優れています(そして各呼び出しの環境に基づいて選択的に関数をインライン化できます)。
David Thornley、

@DavidThornley O3フラグが設定されている場合でも、定義がcppファイルにある場合、コンパイラーは関数をインライン化しません。したがって、私が従う大まかなルールは、1つのライナーをインライン化し、ループのない関数もインライン化することです。
talekeDskobeDa

回答:


210

関数呼び出しのコストを回避することは、話の半分にすぎません。

行う:

  • inline代わりに使用#define
  • 非常に小さな関数が良い候補ですinline:より高速なコードと小さな実行可能ファイル(コードキャッシュにとどまる可能性が高くなります)
  • 関数は小さく頻繁に呼び出されます

しないでください:

  • 大きな関数:実行可能ファイルが大きくなり、呼び出しのオーバーヘッドによる高速実行に関係なく、パフォーマンスが大幅に低下します。
  • I / Oバウンドのインライン関数
  • 関数はめったに使用されません
  • コンストラクタとデストラクタ:空の場合でも、コンパイラはそれらのコードを生成します
  • ライブラリ開発時のバイナリ互換性の破壊:
    • 既存の関数をインライン化する
    • インライン関数を変更するか、インライン関数を非インラインにする:ライブラリの以前のバージョンは古い実装を呼び出します

ライブラリを開発する場合、将来クラスを拡張可能にするために、次のことを行う必要があります。

  • 本体が空でも非インラインの仮想デストラクタを追加します
  • すべてのコンストラクターを非インラインにします
  • クラスを値でコピーできない場合を除き、コピーコンストラクターと代入演算子の非インライン実装を記述します。

inlineキーワードはコンパイラーへのヒントであることを忘れないでください。コンパイラーは関数をインライン化しないことを決定する場合がありinline、最初にマークされていない関数をインライン化することを決定する場合があります。通常、マーキング機能は避けますinline(非常に小さな関数を書くときは別として)。

パフォーマンスについては、賢明なアプローチは(いつものように)アプリケーションをプロファイリングすることです。 inlineプロファイリングし的にはボトルネックを表す一連の関数をプロファイリングすることです。

参照:


編集:Bjarne Stroustrup、C ++プログラミング言語:

関数はと定義できますinline。例えば:

inline int fac(int n)
{
  return (n < 2) ? 1 : n * fac(n-1);
}

inline指定子は、それがの呼び出しのためのコードを生成しようとすべきであることをコンパイラへのヒントですfac()一度関数のコードを敷設して、通常の関数呼び出しメカニズムを通じて呼び出すよりもインラインではなく。賢いコンパイラは720、呼び出し用の定数を生成できますfac(6)。相互に再帰的なインライン関数、入力に応じて再帰するかしないかに依存するインライン関数などの可能性により、inline関数のすべての呼び出しが実際にインライン化されることを保証することが不可能になります。コンパイラーの賢さの程度を規定することはできません。そのため、あるコンパイラーが生成し720、別のコンパイラーが、さらに別の6 * fac(5)インライン化されていない呼び出しを生成する可能性がありますfac(6)

異常に賢いコンパイルおよびリンク機能がない場合にインライン化を可能にするには、インライン関数の定義(宣言だけでなく)がスコープ内になければなりません(§9.2)。inlineespecifierは、関数の意味論に影響を与えません。特に、インライン関数は依然として一意のアドレスを持っているためstatic、インライン関数の変数(§7.1.2)があります。

EDIT2:ISO-IEC 14882-1998、7.1.2関数指定子

指定子付きの関数宣言(8.3.5、9.3、11.4)はinline、インライン関数を宣言します。インライン指定子は、呼び出しの時点での関数本体のインライン置換が通常の関数呼び出しメカニズムよりも優先されることを実装に示します。呼び出しの時点でこのインライン置換を実行するための実装は必要ありません。ただし、このインライン置換を省略した場合でも、7.1.2で定義されているインライン関数の他のルールは引き続き尊重されます。


34
inlineコンパイラへのヒント以上のものです。複数の定義に関する言語規則を変更します。また、静的データがあることは、関数のインライン化を回避するための鋳鉄の理由ではありません。実装では、関数が宣言されているかどうかにかかわらず、各関数staticに単一の静的オブジェクトを割り当てるinline必要があります。インラインコンストラクターと仮想デストラクタがある場合でも、クラスは拡張可能です。また、空のブレースデストラクタは仮想関数の1つであり、インラインのままにしておくことをお勧めします。
CBベイリー

2
これは、関数が必ずしもインライン化されないという意味でのヒントです(ただし、英語は私の母国語ではありません)。とマークされた関数の静的についてはinline、その結果、関数はインライン化されません。呼び出しの代金を支払うだけでなく、関数を含み、関数を呼び出す各翻訳単位は、コードと静的変数の独自のコピーを取得します。ライブラリの開発時にコンストラクタとデストラクタをインライン化しない理由は、ライブラリの将来のバージョンとのバイナリ互換性です
Gregory Pakosz

14
これを「コンパイラへのヒント」と呼ぶのは不正確です。実際には、inlineコンパイラーがそのように感じた場合、非関数をインライン化できます。またinline、コンパイラーがインライン化しないことを決定した場合、関数はインライン化されません。チャールズベイリーが言ったように、それは言語規則を変更します。それを最適化のヒントとして考えるのではなく、完全に異なる概念として考える方が正確です。このinlineキーワードは、複数の定義のみを許可するようコンパイラーに指示します。「インライン化」最適化は、マークされているかどうかに関係なく、ほとんどすべての関数に適用できますinline
jalf

26
それは、Stroustrupが「インライン指定子はコンパイラへのヒントである」と書いているとき、私が彼を引用したことで非難されていることに驚いているだけです。とにかく、私はこの回答をできる限り多くの参照で
裏付ける

2
@GregoryPakosz:ただし、inline関数のインライン化を取得するためにすべてを使用するわけではありません。ODRを回避するなど、他の利点が必要になる場合があります。
2013

57

inline 最適化とはほとんど関係がありません。 inline与えられた定義がプログラム内で複数回発生した場合にエラーを生成しないようにコンパイラーに指示します。定義は、使用されるすべての変換で発生し、出現するすべての場所でまったく同じ定義になります。

上記のルールを考えると、inline宣言が必要とするものに対する追加の依存関係を本文に含める必要がない短い関数に適しています。定義に遭遇するたびにそれを解析し、その本体のコードを生成する必要があるため、単一のソースファイルで1回だけ定義された関数に対するコンパイラオーバーヘッドが発生します。

コンパイラーは、選択した関数呼び出しをインライン化(つまり、関数の呼び出しをその関数のアクションを実行するコードに置き換える)できます。以前は、呼び出しと同じ変換単位で宣言されていない関数を「明らかに」インライン化できなかったが、リンク時間の最適化の使用が増えたため、これは今では真実ではない。同様に、マークされた関数inlineがインライン化されない可能性があることも事実です。


これは、C ++の意図的な機能というよりは、偶然の一致のようです。この考え方は、Cの「静的」グローバル変数に非常に似ています。ただし、これは非常に興味深い答えです。彼らが「内部」のようなキーワードを使って内部のつながりを示していたらいいのにと思います。
レーノリンデケ2009

+1。@レーノ:あなたの言っていることが本当にわかりません。リンケージはinlineキーワードとどのような関係がありますか?そして、幸せな偶然とは何ですか?
jalf

@jalf:自分のコメントを振り返ってみると、あいまいで、よく考えられていないことに気づきました。同じ関数を複数のファイルで定義すると、リンカーエラーが発生します。これは、関数を「静的」に宣言することで対処できます。ただし、「インライン」を使用すると、「静的」のように実際には内部リンケージを取得しないという微妙な違いで同じことを実行できます。言語インプリメンター/デザイナーは、ヘッダーファイルで宣言された関数で特別なことを行う必要があり、それが「インライン」に引き継がれる必要があることに気付いたので、これは実際には偶然ではないかと思います。
Rehno Lindeque 2009

4
インラインで使用する主な理由はパフォーマンスであるため、コメントがなぜ多くの賛成票を得たのかわかりません。
gast128 2017

10

関数をインライン展開するようコンパイラーに指示することは最適化であり、最適化の最も重要なルールは、早期の最適化がすべての悪の根源であることです。常に明確なコードを記述し(効率的なアルゴリズムを使用)、プログラムのプロファイルを作成し、時間がかかりすぎる関数のみを最適化します。

特定の関数が非常に短くて単純で、内側の狭いループで何万回も呼び出される場合は、その関数が適している可能性があります。

ただし、驚くかもしれません-多くのC ++コンパイラーは小さな関数を自動的にインライン化します-そして、インライン化の要求も無視する可能性があります。


実際、特定のコンパイラーが「インライン」を完全に無視し、「__ inline」または「__force_inline」にのみ応答するのではないかと疑っています。これは虐待を防ぐためだと思います!
レーノリンデケ2009

通常はそうではありません。インラインは単なるヒントですが、ほとんどのコンパイラが真剣に考えているヒントです。コンパイラーをオブジェクトコードと共に(/FAcsVisual Studio、-sGCCで)生成するようにコンパイラーを設定して、コンパイラーが何をするかを正確に確認できます。私の経験では、これらのコンパイラはどちらもインラインキーワードの重みをかなり重視しています。
Crashworks、

1
私の経験では、g ++もVCもinlineキーワードの重み付けをまったくしていないので、興味深いです。つまり、インライン化されている関数を確認inlineし、そこから指定子を削除しても、関数はインライン化されます。反対の具体的な例がある場合は、それらを共有してください!
Pavel Minaev、2009

4
inlineキーワードは「明確なコード」をどのように妨げますか?「時期尚早の最適化」のキーワードは時期尚早であり、最適化ではありません*最適化を積極的に避けるべきだと言っているのは、ごみです。その引用の要点は、必要ではない可能性のある最適化を避け、コードに有害な副作用(保守性を低下させるなど)を避ける必要があることです。inlineキーワードがコードの保守性を低下させる方法、または関数に追加することでどのように害を及ぼす可能性があるのか、私にはわかりません。
2009

3
jalf、関数をインライン化すると、コードが遅くなるが、速くはならない。1つの例は、関数がコード内のいくつかの異なる場所から呼び出される場合です。関数がインライン化されていない場合は、別の場所から呼び出されたときに命令キャッシュに残っている可能性があり、分岐予測子はすでにウォームアップされている可能性があります。常に効率を向上させるいくつかのパターンがあるので、それらを使用しても害はありません。インライン化はそれらの1つではありません。それは通常、パフォーマンスにまったく影響を与えません、それは時には助け、時には痛いです。私は私のアドバイスの後ろに立ちます:最初にプロファイル、次にインライン。
dmazzoni 2009

5

見つける最良の方法は、プログラムのプロファイルを作成し、何度も呼び出される小さな関数にマークを付けて、CPUサイクルをとして実行することinlineです。ここでのキーワードは「小さい」です。関数呼び出しのオーバーヘッドが関数で費やされた時間と比較して無視できるほどになると、それらをインライン化しても意味がありません。

私が提案する他の使用法は、キャッシュミスに関連するほど頻繁にパフォーマンスが重要なコードで呼び出される小さな関数がある場合、おそらくそれらもインライン化する必要があるということです。繰り返しになりますが、プロファイラーがそれを伝えることができるはずです。


4

時期尚早の最適化はすべての悪の根源です!

経験則として、私は通常「ゲッター」と「セッター」のみをインライン化します。コードが機能して安定すると、プロファイリングにより、インライン化のメリットが得られる関数を特定できます。

一方、最新のコンパイラのほとんどは非常に優れた最適化アルゴリズムを備えており、インライン化する必要があるものをインライン化します。

復活-インラインの1行関数を記述し、後で他の関数について心配する。


2

インライン関数すると、スタックに引数をプッシュする必要がなくなるため、コードのパフォーマンス向上するあります。問題の関数がコードの重要な部分にある場合は、プロジェクトの最適化部分でインラインではなくインラインの決定を行う必要があります。

あなたはC ++の FAQでインラインについてもっと読むことができます


1

インライン関数は、最適化としてではなく、コードを読みやすくするためによく使用します。コード自体は、コメントや説明的な名前などよりも短く、理解しやすい場合があります。次に例を示します。

void IncreaseCount() { freeInstancesCnt++; }

読者はすぐにコードの完全なセマンティクスを知ることができます。


0

私は一般的に、3〜4個の単純なステートメントをインラインとして関数を作成するという経験則に従っています。ただし、これはコンパイラへのヒントにすぎないことを覚えておいてください。インラインにするかどうかの最後の呼び出しは、コンパイラーのみが行います。これらのより多くのステートメントがある場合、愚かなコンパイラの場合と同様にインラインで宣言しないため、コードが膨張する可能性があります。


0

最適な方法は、インライン化されたものとインライン化されていないものの生成された命令を調べて比較することです。ただし、を省略しても常に安全inlineです。使用inlineすると、望ましくないトラブルが発生する可能性があります。


0

インラインを使用するかどうかを決定するときは、通常、次のことを念頭に置いてください。最新のマシンでは、メモリ遅延は生の計算よりも大きなボトルネックになる可能性があります。頻繁に呼び出されるインライン関数は、実行可能ファイルのサイズが大きくなることが知られています。さらに、そのような関数はCPUのコードキャッシュに格納でき、そのコードにアクセスする必要があるときにキャッシュミスの数を減らすことができます。

したがって、自分で決める必要があります。インライン化によって、生成されるマシンコードのサイズが増加または減少しますか?関数を呼び出すとキャッシュミスが発生する可能性はどのくらいありますか?コード全体にペッパーが付いている場合、可能性は高いと言えます。単一のタイトループに制限されている場合、可能性は低くなるはずです。

私は通常、以下にリストするケースではインライン化を使用します。ただし、パフォーマンスに本当に関心がある場合は、プロファイリングが不可欠です。さらに、コンパイラーが実際にヒントを取得するかどうかを確認することもできます。

  • タイトなループで呼び出される短いルーチン。
  • 非常に基本的なアクセサー(get / set)とラッパー関数。
  • ヘッダーファイル内のテンプレートコードは、残念ながら自動的にインラインヒントを取得します。
  • マクロのように使用される短いコード。(例:min()/ max())
  • 短い数学ルーチン。

0

また、インラインメソッドは、大規模なプロジェクトを維持するときに重大な副作用があります。インラインコードが変更されると、それを使用するすべてのファイルがコンパイラーによって自動的に再ビルドされます(これは優れたコンパイラーです)。これは開発時間の多くを無駄にする可能性があります。

ときinlineの方法は、ソース・ファイルに転送し、任意のより多くのインライン化されていない、プロジェクト全体は(少なくともこれは私の経験をされている)を再構築する必要があります。また、メソッドがインラインに変換される場合も同様です。


1
それは別の問題です。ヘッダーファイルに配置されたコードの再構築の問題が発生します。マークが付けられているかどうかinlineは関係ありません(inlineキーワードがない場合を除いて、リンカーエラーが発生しますが、inlineキーワードは過剰な再構築を引き起こす問題ではありません。)
jalf

ただし、インラインメソッドを変更すると、shourceファイルの非インラインメソッドを変更するのではなく、ビルドが過剰になります。
Thomas Matthews

0

関数コードが小さい場合にのみインライン関数修飾子を使用する必要があります。関数が大きい場合は、メモリ空間の節約は実行速度の比較的小さな犠牲に値するため、通常の関数を使用する必要があります。


0

コードがインラインとして使用できるほど小さく、インライン関数を覚えている場合は、コードを複製して貼り付け、関数が呼び出されたときに貼り付けるので、実行時間を増やすだけでなく、メモリ消費量も増える可能性があります。loop / static変数/ recursive / switch / goto / Virtual関数を使用している場合は、インライン関数を使用できません。仮想は実行時まで待機することを意味し、インラインはコンパイル中を意味するため、同時に使用することはできません。


-2

私はいくつかの回答を読みましたが、不足しているものがいくつかあります。

私が使用するルールは、インラインにしたくない限り、インラインを使用しないことです。ばかげて見える、今の説明。

コンパイラーは十分にスマートで、短い関数は常にインラインになります。そして、プログラマーがそうするように言わない限り、長い関数をインラインとして機能させないでください。

インラインはコンパイラへのヒントまたは要求であることを知っています

実際にinlineはコンパイラの順序であり、選択肢はなく、inlineキーワードの後はすべてのコードをインラインにします。したがって、inlineキーワードを使用することはできません。コンパイラは最短のコードを設計します。

だからいつ使うのinline

コードをインライン化したい場合に使用します。1つの状況でしか使用しないので、私は1つの例しか知りません。ユーザー認証です。

たとえば、私はこの機能を持っています:

inline bool ValidUser(const std::string& username, const std::string& password)
{
    //here it is quite long function
}

この関数がどれほど大きい場合でも、ソフトウェアをクラックしにくくするため、インラインとして使用したいと考えています。


2
インラインはまだヒントです。関数が肥大化していると見なされた場合、コンパイラーはインライン化に失敗する可能性があります。
It'sPete

1つはインラインが注文だと言います...もう1つはそのヒントと言います誰かが彼のステートメントを実証して、どれが正しいかを判断できるようにしますか?

@ user2918461インラインでのステートメントのサポートは単なるヒントです。これは多くのウェブサイトや書籍でサポートされています
WARhead 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.