31

私は C/C++ 開発者ですが、いつも困惑する質問がいくつかあります。

  • 「通常の」コードとインライン コードに大きな違いはありますか?
  • 主な違いはどれですか?
  • インライン コードは単なるマクロの「形式」ですか?
  • コードをインライン化する場合、どのようなトレードオフを行う必要がありますか?

ありがとう

4

16 に答える 16

43

パフォーマンス

以前の回答で示唆されたように、inlineキーワードを使用すると、関数呼び出しをインライン化することでコードを高速化できますが、多くの場合、実行可能ファイルが増加します。「関数呼び出しのインライン化」とは、引数を適切に入力した後、ターゲット関数の呼び出しを関数の実際のコードに置き換えることを意味します。

ただし、最新のコンパイラは、高度な最適化に設定されている場合、ユーザーからのプロンプトなしで関数呼び出しを自動的にインライン展開するのに非常に優れています。実際、コンパイラは通常、人間よりも高速化のためにインラインへの呼び出しを判断する方が優れています。

inlineパフォーマンス向上のために関数を明示的に宣言することは (ほとんど?) 常に不要です!

さらに、コンパイラは、要求が適合する場合、要求を無視することができ、無視 inlineます。関数への呼び出しをインライン化できない場合 (つまり、重要な再帰または関数ポインターを使用する場合) だけでなく、関数が単純に大きすぎて意味のあるパフォーマンスが得られない場合にも、コンパイラーはこれを行います。

1 つの定義ルール

ただし、inlineキーワードを使用してインライン関数を宣言すると、他の効果があり、実際には One Definition Rule (ODR) を満たす必要がある場合があります。C++ 標準のこの規則は、特定のシンボルを複数回宣言できますが、定義できるのは 1 回だけであると述べています。 . リンカー (= リンカー) が複数の同一のシンボル定義に遭遇すると、エラーが生成されます。

この問題の解決策の 1 つは、コンパイル ユニットが特定のシンボルをエクスポートしないようにすることstaticです。

inlineただし、多くの場合、代わりに関数をマークする方が適切です。これにより、リンカーは、コンパイル ユニット全体でこの関数のすべての定義を 1 つの定義にマージし、1 つのアドレスと共有の関数静的変数を使用するように指示されます。

例として、次のプログラムを考えてみましょう。

// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP

#include <cmath>
#include <numeric>
#include <vector>

using vec = std::vector<double>;

/*inline*/ double mean(vec const& sample) {
    return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}

#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"

#include <iostream>
#include <iomanip>

void print_mean(vec const& sample) {
    std::cout << "Sample with x̂ = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"

void print_mean(vec const&); // Forward declaration.

int main() {
    vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
    print_mean(x);
}

両方の.cppファイルにヘッダー ファイルが含まれているため、 の関数定義が含まれていることに注意してくださいmean。ファイルは、二重インクルードに対するインクルード ガードを使用して保存されますが、これにより、コンパイル単位が異なるにもかかわらず、同じ関数の 2 つの定義が作成されます。

ここで、これら 2 つのコンパイル ユニットをリンクしようとすると、たとえば次のコマンドを使用します。

⟩⟩⟩ g++ -std=c++11 -pedantic main.cpp test.cpp

「重複したシンボル __Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE」というエラーが表示されます (これは関数のマングルmeanです)。

ただし、関数定義の前にある修飾子のコメントを外すinlineと、コードは正しくコンパイルおよびリンクされます。

関数テンプレートは特殊なケースです。そのように宣言されているかどうかに関係なく、常にインラインです。これは、コンパイラーが呼び出しをインライン化するという意味ではありませんが、ODR に違反することはありません。クラスまたは構造体内で定義されているメンバー関数についても同じことが言えます。

于 2008-09-25T13:43:43.763 に答える
41
  • 「通常の」コードとインライン コードに大きな違いはありますか?

はいといいえ。いいえ、インライン関数またはメソッドは通常の関数またはメソッドとまったく同じ特性を持っているため、最も重要なことは、両方ともタイプ セーフであることです。はい、コンパイラによって生成されるアセンブリ コードが異なるためです。通常の関数では、各呼び出しはいくつかのステップに変換されます。スタックにパラメーターをプッシュする、関数にジャンプする、パラメーターをポップするなどですが、インライン関数への呼び出しは、大きい。

  • インライン コードは単なるマクロの「形式」ですか?

いいえ!マクロは単純なテキスト置換であり、重大なエラーにつながる可能性があります。次のコードを検討してください。

#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )

[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]

インライン関数を使用すると、関数が実際に実行される前にパラメーターが評価されることが確実になります。また、型チェックも行われ、最終的には仮パラメーターの型に一致するように変換されます。

  • コードをインライン化する場合、どのようなトレードオフを行う必要がありますか?

通常、インライン関数を使用するとプログラムの実行は速くなりますが、バイナリ コードが大きくなります。詳細については、GoTW#33を参照してください。

于 2008-09-25T12:10:46.107 に答える
16

インライン コードは本質的にマクロのように機能しますが、最適化可能な実際のコードです。関数呼び出しをセットアップする (パラメーターを適切なレジスターにロードする) ために必要な作業は、メソッドが実際に行う少量の作業に比べてコストがかかるため、非常に小さな関数はインライン化に適していることがよくあります。インライン化では、関数呼び出しを設定する必要はありません。コードは、それを使用するメソッドに直接「貼り付け」られるからです。

インライン化によりコード サイズが増加しますが、これが主な欠点です。コードが大きすぎて CPU キャッシュに収まらない場合は、速度が大幅に低下する可能性があります。非常に多くの場所でメソッドを使用している場合、コードの増加によって問題が発生する可能性は低いため、これについて心配する必要があるのはまれなケースだけです。

要約すると、インライン化は、何度も呼び出されるがあまり多くの場所で呼び出されない小さなメソッドを高速化するのに理想的です (ただし、100 か所でも問題ありません。大幅なコードの肥大化を得るには、非常に極端な例に入る必要があります)。

編集:他の人が指摘したように、インライン化はコンパイラへの提案にすぎません。巨大な 25 行のメソッドをインライン化するような愚かな要求を行っていると判断した場合は、自由に無視できます。

于 2008-09-25T11:36:29.373 に答える
7
  • 「通常の」コードとインライン コードに大きな違いはありますか?

はい - インライン コードは関数呼び出しを伴わず、レジスタ変数をスタックに保存します。「呼び出される」たびにプログラム空間を使用します。したがって、プロセッサでの分岐や状態の保存、キャッシュのクリアなどがないため、全体として実行にかかる時間が短くなります。

  • インライン コードは単なるマクロの「形式」ですか?

マクロとインライン コードには類似点があります。大きな違いは、インライン コードが関数として特別にフォーマットされているため、コンパイラや将来のメンテナがより多くのオプションを利用できることです。具体的には、コンパイラにコードスペースを最適化するように指示した場合、または将来のメンテナーがそれを拡張してコード内の多くの場所で使用することになる場合、簡単に関数に変換できます。

  • コードをインライン化する場合、どのようなトレードオフを行う必要がありますか?

    • マクロ: コード空間の使用量が多い、実行が速い、「関数」が長いと保守が難しい
    • 機能: コードスペースの使用量が少ない、実行が遅い、保守が容易
    • インライン関数: コード空間の使用率が高く、実行が速く、保守が容易

レジスタの保存と関数へのジャンプはコード スペースを占有するため、非常に小さな関数の場合、インラインは関数よりも占有するスペースが少ないことに注意してください。

-アダム

于 2008-09-25T11:40:08.677 に答える
2

それはコンパイラに依存します...
あなたがダムコンパイラを持っているとしましょう。関数をインライン化する必要があることを示すことにより、呼び出されるたびに関数の内容のコピーが配置されます。

利点: 関数呼び出しのオーバーヘッドがありません (パラメーターの入力、現在の PC のプッシュ、関数へのジャンプなど)。たとえば、大きなループの中央部分で重要になることがあります。

不便: 生成されたバイナリを膨張させます。

マクロですか?コンパイラは依然としてパラメーターの型などをチェックするため、そうではありません。

スマートコンパイラはどうですか?関数が複雑すぎる/大きすぎると「感じる」場合は、インライン ディレクティブを無視できます。また、単純なゲッター/セッターなどの簡単な関数を自動的にインライン化することもできます。

于 2008-09-25T11:38:35.887 に答える
2

インラインはマクロとは異なり、コンパイラへのヒントであり (コンパイラはコードをインライン化しないことを決定する場合があります!)、マクロはコンパイル前にソース コード テキストを生成するため、インライン化が「強制」されます。

于 2008-09-25T11:41:18.767 に答える
1

関数をインラインとしてマークするということは、コンパイラがそのようにすることを選択した場合に、関数が呼び出される場所で「インライン」に含めるオプションがコンパイラにあることを意味します。対照的に、マクロは常にその場で展開されます。マクロのデバッグは混乱を招きますが、インライン関数には適切なデバッグ シンボルが設定されており、シンボリック デバッガーがソースのソースを追跡できるようになっています。インライン関数は有効な関数である必要がありますが、マクロは...そうではありません。

関数をインラインで宣言することを決定することは、主にスペースのトレードオフです。コンパイラがインライン化することを決定した場合、プログラムは大きくなります (特に静的でない場合。この場合、インライン化されていないコピーが少なくとも 1 つ必要です。外部オブジェクト); 実際、関数が大きい場合、キャッシュに収まるコードが少なくなるため、パフォーマンスが低下する可能性があります。ただし、一般的なパフォーマンスの向上は、関数呼び出し自体のオーバーヘッドを取り除くことです。内部ループの一部として呼び出される小さな関数の場合、これは理にかなったトレードオフです。

コンパイラを信頼する場合は、内部ループで使用される小さな関数をinline自由にマークしてください。コンパイラは、インライン化するかどうかを決定する際に正しいことを行う責任があります。

于 2008-09-25T11:40:04.667 に答える
0

まず、インラインは、関数をインライン化するようにコンパイラーに要求します。したがって、インライン化するかどうかはコンパイラー次第です。

  1. いつ使用するか関数が非常に少ない行(すべてのアクセサーとミューテーターに対して)であるが、再帰関数に対してはない場合
  2. 利点?関数呼び出しの呼び出しにかかる時間は関係ありません
  3. コンパイラは独自の関数をインライン化しますか?はい、クラス内のヘッダーファイルで関数が定義されている場合はいつでも可能です
于 2008-09-25T12:28:00.950 に答える
0

インラインコードの方が高速です。関数呼び出しを実行する必要はありません(すべての関数呼び出しには時間がかかります)。欠点は、関数が実際には関数として存在せず、したがってポインターがないため、インライン関数へのポインターを渡すことができないことです。また、関数をパブリックにエクスポートすることはできません(たとえば、ライブラリ内のインライン関数は、ライブラリにリンクしているバイナリ内では使用できません)。もう1つは、さまざまな場所から関数を呼び出すと、バイナリのコードセクションが大きくなることです(関数のコピーが1つだけあり、常にそこにジャンプするのではなく、関数のコピーが生成されるたびに)

通常、関数をインライン化するかどうかを手動で決定する必要はありません。たとえば、GCCは、最適化レベル(-Ox)および他のパラメーターに応じて自動的に決定します。「関数の大きさ」などを考慮します。(命令の数)、コード内で呼び出される頻度、バイナリをインライン化することでバイナリがどれだけ大きくなるか、およびその他のメトリック。たとえば、関数が静的であり(したがって、とにかくエクスポートされない)、コード内で1回だけ呼び出され、関数へのポインターを使用しない場合、悪影響がないため、GCCが自動的にインライン化することを決定する可能性があります(バイナリは、一度だけインライン化しても大きくなりません)。

于 2008-09-25T13:20:39.263 に答える
0

インライン化する必要があるかどうかの答えは、速度にかかっています。関数を呼び出すタイトなループにあり、それが非常に巨大な関数ではなく、関数の呼び出しに多くの時間が費やされている場合は、その関数をインラインにすると、多くの効果が得られますあなたのお金。

于 2008-09-25T12:13:29.563 に答える
0

コードを fe C++ でインラインとしてマークしている場合は、コードをインラインで実行する必要があることもコンパイラに伝えています。そのコードブロックは、呼び出された場所に「多かれ少なかれ」挿入されます(したがって、スタックでのプッシュ、ポップ、およびジャンプが削除されます)。そのため、はい...関数がそのような動作に適している場合はお勧めです。

于 2008-09-25T11:37:48.667 に答える
0

「インライン」は、2000 年代の「登録」に相当するものです。何を最適化するかを決定するのは、ユーザーよりもコンパイラの方が優れています。

于 2008-09-25T11:38:31.770 に答える
0

インライン化は、速度を上げるための手法です。ただし、プロファイラーを使用して、状況でこれをテストしてください。私は (MSVC) インライン化が常に配信されるとは限らず、確かに素晴らしい方法で配信されるわけではないことを発見しました。実行時間は数パーセント減少することもありましたが、わずかに異なる状況では数パーセント増加しました。

コードの実行が遅い場合は、プロファイラーを取り出してトラブルスポットを見つけて作業してください。

ヘッダー ファイルにインライン関数を追加するのをやめました。これにより結合が増加しますが、見返りはほとんどありません。

于 2008-09-25T12:56:46.357 に答える
0

インライン化により、コンパイラは関数の実装を呼び出しポイントに挿入します。これでやっていることは、関数呼び出しのオーバーヘッドを取り除くことです。ただし、インライン化のすべての候補が実際にコンパイラによってインライン化されるという保証はありません。ただし、小さい関数の場合、コンパイラは常にインライン化します。したがって、関数が何度も呼び出されるが、コードの量が限られており (数行) しかない場合は、関数呼び出しのオーバーヘッドが関数自体の実行よりも長くかかる可能性があるため、インライン化のメリットがあります。

インライン化の適切な候補の典型的な例は、単純な具象クラスのゲッターです。

CPoint
{
  public:

    inline int x() const { return m_x ; }
    inline int y() const { return m_y ; }

  private:
    int m_x ;
    int m_y ;

};

一部のコンパイラ ( VC2005 など) には積極的なインライン化のオプションがあり、そのオプションを使用する場合は 'inline' キーワードを指定する必要はありません。

于 2008-09-25T11:43:13.367 に答える
0

上記のことは繰り返しませんが、呼び出された関数は実行時に解決されるため、仮想関数はインライン化されないことに注意してください。

于 2008-09-25T11:56:40.400 に答える
0

インライン化は通常、最適化のレベル 3 (GCC の場合は -O3) で有効になります。場合によっては(可能な場合)、速度が大幅に向上する可能性があります。

プログラムで明示的なインライン化を行うと、コード サイズが増加するという代償を伴い、速度が向上する可能性があります。

コードのサイズと速度のどちらが適しているかを確認し、それをプログラムに含めるかどうかを決定する必要があります。

レベル 3 の最適化を有効にするだけで、それを忘れて、コンパイラーに仕事を任せることができます。

于 2008-09-25T11:57:56.857 に答える