はじめに: 機能の呼出しの頭上を評価する再評価

Cプログラミングでは、すべての関数呼び出しはオーバーヘッドを導入します。コンパイラは、スタック(またはレジスタでそれらを渡す)に引数をプッシュし、関数のボディにジャンプし、コードを実行し、そして戻ります。 小さくて頻繁に呼び出された関数の場合、このオーバーヘッドは、パフォーマンスクリティカルループや深くネストされた操作で、実行時間を支配することができます。 現代のコンパイラは、積極的に最適化しますが、時にはプログラマは、最大速度を達成するために明示的な機能を提供しなければなりません。 そのような1つは、 LTL-1Flin関数を継承することを可能にします。 [F] 関数は、関数を継承する機能が実行することを可能にします。

インライン機能の背後にある機構

インライン関数は [ キーワードで宣言されます。 これは、コンパイラをインラインに命令しません。 提案です。 コンパイラは、あまりにも大きすぎる、再帰的、または最適化レベルが低いときに関数のためにそれを無視することができます。 C99 以降では、]のセマティクスは、 ]で定義された関数は、重複した部分を行なうことなく複数の翻訳単位に含めることができます。 [FLT] または非公開の定義は、多くの場合、通常、 [FLT] の定義された関数が明確に指定されています。

  • [ 静的インライン:[]]]] 機能には内部のリンクが搭載されています。各翻訳ユニットは独自のコピーを取得します。これは、ヘッダーで定義された小さなヘルパー関数の最も安全で最もポータブルなアプローチです。
  • [外部インライン(C99):[]) インライン定義は、整列のための体を提供しますが、外部の定義は別々に存在しなければなりません(通常、.cファイルで)。 C11以降では、この動作は調和しました。
  • []静的または外部:[の無インラインで、これは外部インラインに似ています。 C11では、関数が整っていない場合にのみ、外部の定義が必要です。 実際には、ほとんどのユースケースにが優先されます。

キーインサイト:]]インリンギングは無料のランチではありません。コンパイラは、コストのメリットトレードオフを分析します。すべてのコールサイトで関数のボディを投入すると、指示キャッシュの効率を低下させるコードサイズ(コードの肥大)が増加します。したがって、インラインは、関数と呼ばれる、小規模で予約するのが最善です。

インライン関数Excel: ケースとベストプラクティスを使用する

小規模な数学的操作

正方形の計算、値のクランプ、または記号のテストなどの、小数演算などの小数演算を実行する関数は、主な候補です。関数呼び出しのオーバーヘッドは、多くの場合、動作自体よりも大きいです。例えば:

static inline int clamp(int value, int low, int high) {
 return (value < low) ? low : (value > high) ? high : value;
}

データセンターおよびデータ構造におけるMutator機能

C のオブジェクト指向パターンは、データカプセル化するために、ゲッタとセッターを使うことが多いです。インライン化せずに、これらのトリビア関数は不要なオーバーヘッドを追加します。

typedef struct {
 int x, y;
} Point;

static inline int point_get_x(const Point *p) {
 return p->x;
}

static inline void point_set_x(Point *p, int x) {
 p->x = x;
}

組込みシステムとリアルタイムコード

限られたスタックスペースと決定的なタイミング要件を持つ環境では、インライン関数は、レイテンシとメモリの使用量を削減し、プッシュ/ポップスタックフレームを除去する必要性を排除します。ただし、コードサイズはメモリ制約されたマイクロコントローラで慎重に監視する必要があります。

]の行列に

  • []大関数:]] 複数のコールサイトで100以上の行関数をインライン化すると、バイナリを膨らませ、指示キャッシュ圧力によるパフォーマンスが劣化する可能性があります。
  • ]再帰関数:[]]] 再帰が完全にインライン化できません(コンパイラが数レベルをアンロールする可能性がある場合)。
  • ループ付きの関数:[ 大きいループを含む関数をインライン化すると、大きな利点を提供することができない。
  • :] と呼びます。関数が不均一に呼ばれると、オーバーヘッドは無視されます。 無駄なスペースだけをインライン化します。

インライン関数 Versus マクロ: 詳細な比較

キーワードが標準だった前に、C プログラマは「インライン化」を実現するためにマクロ ([) を使用しましたが、マクロはテキスト置換、関数ではなくです。 それらは深刻な欠点をもたらします。

  • [タイプ安全:]]]マクロはタイプを無視します。 不有名[マクロは、複数の回引数を評価し、]のような式で使用したときに危険な副作用を引き起こします。
  • :]] マクロは処理中に消えます。 デバッグはそれらにステップすることはできません。
  • 複合ステートメント:]] 多方向マクロは、醜い回避策(例えば、)を必要とします。
  • [ 名称衝突:] マクロの拡張は、ローカル変数を干渉することができます。

インライン関数は、これらのすべての問題を克服します。それらは、タイプチェック、スコープ、および副作用の危険引数評価で真の関数です。それらは、正規型システムに参加し、デバッグすることができます。マクロの唯一の理論的利点は、それらは[]タイプジェネリックの操作に使用できることですが、C11 ]とC23 []の提案は、ギャップを低減する場合でも、ということです。

[]親指のルール:[ プリファー[]]は、関数署名に合ったマクロ上の機能です。 簡単な定数やトークンの貼り付けだけのためにマクロを準備します。

実用的な例: 行動のインライン関数

例1:スクエア(既に提供)

static inline int square(int x) {
 return x * x;
}

コンパイラは、呼び出し命令を全く出さない可能性が高い。各呼び出しサイトでは、コードは単に[になります。

例2:文字がディジットの場合のチェック

static inline int is_digit(char c) {
 return c >= '0' && c <= '9';
}

例3: マクロを無効化する(マクロを無効化)

static inline int imax(int a, int b) {
 return (a > b) ? a : b;
}

マクロ版とは異なり、これはとを正確に一度に評価し、二重評価リスクを回避します。

例4:ビット操作(ユニオンまたはバイトスワッピング)

static inline uint16_t swap_bytes(uint16_t x) {
 return (x << 8) | (x >> 8);
}

ARM の の 1 つにコンパイルするか、 インラインに x86 で回転します。

コンパイルの最適化とインラインキーワード

[ キーワードはコンパイラのインライン決定の1つの要因です。ほとんどのコンパイラは、攻撃性を制御するコマンドラインフラグを持っています。

  • [GCC/Clang:] []]は、適度な整列を有効にします。 ]]は、より積極的なインライン化を可能にします。 []]フラグは明示的に有効にすることができます。 コンパイラのヒューリスティックに関係なく、特定の機能のインライン化を強制するには、 を使用して またはより高い。
  • []MSVC:] []]] キーワードが利用可能ですが、インライン化を保証するものではありません(コンパイラは特定の関数を拒否することができます)。

GCC属性例:

static inline __attribute__((always_inline)) int triple(int x) {
 return x * 3;
}

パフォーマンスクリティカルなコードでは、そのインライン化が起きたことを確認するために、生成されたアセンブリ(例えば、GCCの]または])を検査することをお勧めします。 現代のコンパイラは、最適化レベルの高いをマークしない関数をインライン化し、逆に、過剰なコード成長を引き起こす関数のを無視するかもしれません。

潜在的な落札:コードブロアットとバイナリサイズ

さまざまな場所で使用されている関数の呼び出しをインライン化することで、テキストセグメントのサイズを大幅に増加させることができます。これは特に問題です。

  • []ライブラリ:[]]]])ヘッダ内のインライン関数は、それらを含むすべての翻訳ユニットに拡大し、潜在的なコードサイズを乗っ.
  • [] 埋め込みシステム:[] フラッシュとRAMが限られます。 1000 の場所で使用される 10 バイトの関数は、ほぼ 10KB のコードを追加します。
  • 指示キャッシュ:]] より大きなコードは、プログラム全体が遅くなる、より多くのキャッシュのミスを引き起こす可能性があります。

コードの肥大化を緩和するためには、本物に小さな機能(典型的に1〜5ステートメント)のみを使用します。 プロファイルを使用して、盲目にインライン化する前にホット機能を特定します。 実行時間とバイナリサイズの両方を測定します。

C 規格を渡るインライン機能

[[] キーワードが] C99 で導入され、さらに[]C11]と[]]C17[[]で明確に説明しました。 C23は、いくつかの追加改良で同じセマティクスを保持します。 「インライン定義」と「外部の定義」が引き起こされたコンサルの間の歴史的差別化。 現代の慣行では、ほとんどのプロジェクトは、Caftflt:99と、すべてのサブステップで動作します。

プレC99コンパイラ(ますますまれ)をサポートする必要がある場合は、マクロや外部ヘッダーのみの実装に戻りましょう。 それ以外の場合は、ポータブルおよびタイプセーフな代替としてを埋め込む必要があります。

結論:パフォーマンスエンジニアのツールキットの戦略的ツール

インライン関数は、C言語の成熟した、よく定義された機能で、Jediciouslyを適用すると、関数呼び出しのオーバーヘッドを除去し、クロス機能の最適化を有効にすることによって、測定可能な速度の改善を得ることができます。 それらはほぼすべての近代的なコンテキストでマクロよりも優れています。 キーは、小さめ、ホット関数への使用を制限し、プロファイリングとアセンブリ検査による結果を確認することです。 適切なコンパイラフラグと組み合わせることで、インライン関数はCコードを高速かつメンテナンス可能にします。 低レベルのプログラミングでは珍しい組み合わせです。

更に読みたい場合は、インライン関数とのGCCのドキュメントを参照してください。 実際のパフォーマンス分析のために、]このACMキューの記事]は、詳細にトレードオフをインライン化します。