私が読んだことによると、コンパイラーはインライン関数の関数呼び出しをその本体で置き換える義務はありませんが、可能であればそれを行います。これは私に考えさせました-それが事実である場合、なぜ私たちはインラインWordを持っているのですか?すべての関数をデフォルトでインライン関数にして、呼び出しを関数本体で置き換えることができるかどうかをコンパイラーに判断させないのはなぜですか?
inline
はCのものです。 C++の新機能ではありませんでした。
プログラマーがコードの最適化を支援できるように設計されたCキーワード(register
およびinline
)があります。コンパイラーはレジスター割り当てと関数をインライン化するタイミングを決定できるため、これらは今日では一般的に無視されます(実際、コンパイラーは関数をインライン化することもしないこともできます)。最新のプロセッサーでのコード生成は、リッチーがCを発明していたときの一般的な決定論的なプロセッサーでのコード生成よりもはるかに複雑です。
C++でのWordの意味は、複数の同一の定義を持つことができ、それを使用するすべての翻訳単位で定義する必要があるということです。 (つまり、インライン化できることを確認する必要があります。)inline
関数をヘッダーに問題なく含めることができ、クラス定義で定義されたメンバー関数は自動的に実質的にinline
になります。
元々inline
は、関数の呼び出しをインライン化する必要があるという非常に強力なヒントでした。
しかし、inline
の唯一の保証効果は、関数を複数の翻訳単位で(実質的に同一に)定義できるようにすることです。ヘッダーファイルに定義を配置します。
今日、一部のコンパイラーはインライン化のヒントに非常に熱心です。 g ++。そして、一部のコンパイラはそれをそれほど真剣に受け止めません。 Visual C++。しかし、すべては保証を遵守しなければなりません。
残念なことに、これら2つの意味(最適化のヒントと、リンカーレベルの破棄可能な定義と呼ばれることもあります)が同じキーワードに存在しています。
また、inline
(またはそれ以上、破棄可能な定義に関する別のキーワード)をdataに適用できないことも残念です。
ヘッダーのみのモジュールの人気が高まるにつれて、リンカーレベルの破棄可能なデータの必要性が高まっています。たとえば、多くのBoostサブライブラリはヘッダーのみです。
ただし、データについては、テンプレートを使用して少しトリックを適用できます。いくつかのクラステンプレートで定義し、typedef
にテンプレートパラメータvoid
(またはその他)を指定します。これは、1つの定義ルールがテンプレートに対して特定の例外を作成するためです。
注:
¹inline
変数は C++ 17でサポートされる になります。
デフォルトですべての関数をインライン化しないのはなぜですか?それはエンジニアリングのトレードオフだからです。 「最適化」には少なくとも2つのタイプがあります。プログラムの高速化とプログラムのサイズ(メモリフットプリント)の縮小です。インライン化は、一般的に物事をスピードアップします。これは、関数呼び出しのオーバーヘッドを取り除き、スタックからのパラメーターのプッシュとプルを回避します。ただし、すべての関数呼び出しを関数の完全なコードで置き換える必要があるため、プログラムのメモリフットプリントも大きくなります。物事をさらに複雑にするために、CPUは頻繁に使用されるメモリのチャンクをCPU上のキャッシュに保存し、超高速アクセスを実現します。プログラムのメモリイメージを十分に大きくすると、プログラムはキャッシュを効率的に使用できなくなり、最悪の場合、インライン化によってプログラムの速度が実際に低下する可能性があります。ある程度、コンパイラーはトレードオフを計算することができ、ソースコードを見るだけで、あなたができるよりも良い決定を下すことができるかもしれません。
「インライン」を理解するには、履歴を理解する必要があります20年(および30)年前の生活
メモリの少ないコンピュータでコードを書いていたため、コンパイラがプログラムを構成するすべてのコードを一度に処理することは不可能でした。コンパイラーも非常に低速だったため、変更されていないコードを再コンパイルする必要はありませんでした。一部のプロジェクトでは、すべてのコードを再コンパイルするのに24時間以上かかりました(トップエンドの自動車よりもコストがかかるコンピューター)。取り組んだ。
したがって、各コードファイルは個別にオブジェクトファイルにコンパイルされました。各オブジェクトファイルは、関数の「アドレス」とともに、含まれるすべての関数のリストで始まります。オブジェクトファイルには、他のオブジェクトファイルで呼び出されたすべての関数のリストと、呼び出しの場所も含まれていました。
linker は、最初にすべてのオブジェクトファイルを読み取り、それらがエクスポートしたすべての関数のリストを、それらが存在していたファイルとともに作成します。次に、すべての「外部」関数呼び出しを関数のアドレスで更新しながら、すべてのオブジェクトファイルを再度読み取り、プログラムファイルに出力します。
リンカは、外部関数呼び出しへの参照を修正する以外の方法で、コンパイラによって生成されたマシンコードを変更または最適化しませんでした。 リンカーはオペレーティングシステムの一部であり、はほとんどのコンパイラよりも古いものです。人々が新しいコンパイラを書いたとき、彼らは現在のリンカで動作し、現在のオブジェクトファイルにリンクできるようにするためにそれを必要としました。
コンパイラーは、コンパイルされた「.c」または「.cpp」ファイル内のコードと、含まれているすべてのヘッダーファイルを確認しただけです。したがって、他の「.c」または「.cpp」ファイルのコードに基づいて最適化を行うことはできませんでした。
「インライン」キーワードを使用すると、関数(メソッド)の本体をヘッダーファイルで定義できるため、コンパイラーは関数を呼び出すコードをコンパイルするときに関数のコードを利用できます。たとえば、別の.cppファイルで定義されたコレクションクラスがある場合、このクラスには「isEmpty」メソッドがあり、1行のコードが含まれています。関数の呼び出しではなく、結果のプログラムが大幅に高速化されます。 、関数呼び出しはこの1行に置き換えられました。
当時、「インライン」キーワードは、関数呼び出しのコストを回避しながらデータをカプセル化できる「安価で簡単な」方法と見なされていました。オブジェクトのプライベートフィールド。(マクロがはるかに悪い方法は、当時一般的なコードを「インライン化」するコードです。)
最近、「リンカー」は多くのコード最適化を行い、コンパイラーとしていくつかのチームによって書かれる傾向があります。多くの場合、コンパイラーはコードが正しいことを確認して「圧縮」するだけで、マシンコードの作成タスクのほとんどをリンカーに任せます。
標準の内容を確認しましょう(太字で強調されている重要な部分):
2.インライン指定子を含む関数宣言は、インライン関数を宣言します。インライン指定子は、通常の関数呼び出しメカニズムに対して、呼び出しポイントでの関数本体のインライン置換優先することであることを実装に示します。 これを実行するための実装は必要ありません呼び出しの時点でのインライン置換。ただし、このインライン置換を省略した場合でも、インライン関数の他のルールは引き続き尊重されます。
— C++標準、ISO/IEC 14882:20 、7.1.2関数指定子[dcl.fct.spec]
したがって、確実にしたい場合は、コンパイラのドキュメントを読む必要があります。
すべてをインライン化することは悪い考えです。多くの重複したマシンコードが生じる可能性があるためです...
だからあなたは知っておく必要があります:
簡単な答えはありません:何が最善かを確認するには、それを試してみる必要があります。 「
inline
関数を常に使用しない」、「inline
関数を常に使用する」、「inline
関数は、関数がコードのN行未満の場合にのみ機能します。」これらの万能のルールは書き留めるのは簡単かもしれませんが、次善の結果を生み出します。— C++ FAQ、Inline Functions 、9.3
inline
関数はパフォーマンスを向上させますか?
Inlineキーワードを使用する理由を説明しましょう。
チケットプリンターや同様の小型システムなどの組み込みシステム。プロセッサは非常に制限されており、関数呼び出し(スタック上の関数パラメーターの準備、呼び出し、スタックからのパラメーターのフェッチ、応答の返却など)は、関数自体のほかに、実行に数ミリ秒かかる場合があります。
呼び出し時間が約60ミリ秒(実際の関数ではなく呼び出しの場合のみ)で、50回の反復(ツリー内のループ呼び出しまたは反復呼び出し)を実行するとします。
その関数呼び出しから前後に移動する時間は、60 * 50 = 3000(3秒)かかります。
あなたがメモリを持っているなら、あなたは間違いなく3秒を節約するためにインラインをするでしょう。
したがって、インラインは基本的に実行速度が必要な場合に使用されます。私が関わったいくつかのプロジェクトでは、呼び出し時間が実行時間よりも長くなり、インラインを使用する場合の典型的な状況でした。