32

プログラムの複数の部分で呼び出される関数があるとしましょう。また、コードの非常にパフォーマンスに敏感なセクション (たとえば、数千万回反復し、各マイクロ秒がカウントされるループ) にあるその関数への特定の呼び出しがあるとしましょう。他のものをインライン化せずに、コンパイラ(gcc私の場合)にその単一の特定の関数呼び出しをインライン化させる方法はありますか?

編集:これを完全に明確にさせてください。この質問は、 gcc(または他のコンパイラ)に関数へのすべての呼び出しをインライン化させることに関するものではありません。むしろ、コンパイラが関数への特定の呼び出しをインライン化するように要求することについてです。

4

8 に答える 8

14

C では (C++ とは対照的に)、関数をインライン化する必要があることを示す標準的な方法はありません。ベンダー固有の拡張機能のみです。

ただし、コンパイラが常にすべてのインスタンスをインライン化しようとすることがわかっている限り、それを指定しても、その関数は一度だけ使用してください。

オリジナル:

   int MyFunc()  { /* do stuff */  }

への変更:

   inline int MyFunc_inlined()  { /* do stuff */  }

   int MyFunc()  { return MyFunc_inlined(); }

ここで、インライン化したい場所で使用しますMyFunc_inlined()

注: 上記の "inline" キーワードは、gcc がインライン化を強制するために使用する構文の単なるプレースホルダーです。H2CO3 の削除された回答が信頼できるものである場合、それは次のようになります。

static inline __attribute__((always_inline)) int MyFunc_inlined()  { /* do stuff */  }
于 2013-01-28T21:34:10.300 に答える
12

翻訳単位ごとにインライン化を有効にすることができます(呼び出しごとではありません)。これは質問に対する答えではなく、醜いトリックですが、C標準に準拠しており、関連するものとして興味深いかもしれません。

秘訣は、インライン化externしたくないextern inline場所とインライン化が必要な場所で定義を使用することです。

例:

$ cat func.h 
int func();

$ cat func.c 
int func() { return 10; }

$ cat func_inline.h 
extern inline int func() { return 5; }

$ cat main.c       
#include <stdio.h>

#ifdef USE_INLINE
# include "func_inline.h"
#else
# include "func.h"
#endif

int main() { printf("%d\n", func()); return 0; }

$ gcc main.c func.c && ./a.out
10                                                // non-inlined version

$ gcc main.c func.c -DUSE_INLINE && ./a.out
10                                                // non-inlined version

$ gcc main.c func.c -DUSE_INLINE -O2 && ./a.out
5                                                 // inlined!

に依存する代わりに、非標準属性 ( __attribute__(always_inline))GCC など) を定義に使用することもできます。extern inline-O2

ところで、トリックはglibc で使用されます。

于 2015-09-17T17:04:53.723 に答える
6

C で関数を強制的にインライン化する従来の方法は、関数をまったく使用せず、マクロのような関数を使用することでした。このメソッドは常に関数をインライン化しますが、関数のようなマクロにはいくつかの問題があります。例えば:

#define ADD(x, y) ((x) + (y))
printf("%d\n", ADD(2, 2));

C99 標準で C に追加されたinlineキーワードもあります。特に、Microsoft の Visual C コンパイラは C99 をサポートしていないため、その (惨めな) コンパイラでインラインを使用することはできません。インラインは、関数をインライン化する必要があることをコンパイラに示唆するだけであり、それを保証するものではありません。

GCC には、関数をインライン化するためにコンパイラを必要とする拡張機能があります。

inline __attribute__((always_inline)) int add(int x, int y) {
    return x + y;
}

これをよりきれいにするために、マクロを使用することをお勧めします。

#define ALWAYS_INLINE inline __attribute__((always_inline))
ALWAYS_INLINE int add(int x, int y) {
    return x + y;
}

特定の呼び出しで強制的にインライン化できる関数を持つ直接的な方法を知りません。ただし、次のような手法を組み合わせることができます。

#define ALWAYS_INLINE inline __attribute__((always_inline))
#define ADD(x, y) ((x) + (y))
ALWAYS_INLINE int always_inline_add(int x, int y) {
    return ADD(x, y);
}

int normal_add(int x, int y) {
    return ADD(x, y);
}

または、次のようにすることもできます。

#define ADD(x, y) ((x) + (y))
int add(int x, int y) {
    return ADD(x, y);
}

int main() {
    printf("%d\n", ADD(2,2));    // always inline
    printf("%d\n", add(2,2));    // normal function call
    return 0;
}

また、関数のインライン化を強制しても、コードが高速化されない場合があることに注意してください。インライン関数を使用すると、より大きなコードが生成されるため、キャッシュ ミスがさらに発生する可能性があります。それが役立つことを願っています。

于 2013-01-28T23:04:15.343 に答える
4

答えは、機能、要求内容、および機能の性質によって異なります。あなたの最善の策は次のとおりです。

  • インライン化することをコンパイラに伝えます
  • 関数を静的にします (一部のモードでは gcc でセマンティクスが少し変わるため、extern には注意してください)。
  • インライン化する必要があることをオプティマイザに通知するコンパイラ オプションを設定し、インライン制限を適切に設定します。
  • コンパイラでインライン化できなかったという警告をオンにします
  • 関数がインライン化されている出力を確認します (生成されたアセンブラーを確認できます)。

コンパイラのヒント

ここでの回答は、コンパイラへの言語ヒントであるインライン化の片側だけをカバーしています。標準が言うとき:

関数をインライン関数にすることは、関数の呼び出しが可能な限り高速であることを示唆しています。そのような提案が有効である範囲は実装定義です

これは、次のような他の強力なヒントの場合に当てはまります。

  • GNU の__attribute__((always_inline)): 通常、最適化が指定されていない限り、関数はインライン化されません。インラインで宣言された関数の場合、最適化レベルが指定されていなくても、この属性は関数をインライン化します。
  • Microsoft __forceinline: __forceinline キーワードは、費用対効果の分析を上書きし、代わりにプログラマーの判断に依存します。__forceinline を使用するときは注意してください。__forceinline をむやみに使用すると、パフォーマンスがわずかに向上するだけでコードが大きくなり、場合によってはパフォーマンスが低下することさえあります (たとえば、より大きな実行可能ファイルのページングの増加が原因で)。

これらの両方でさえ、可能なインライン展開に依存し、決定的にコンパイラ フラグに依存します。インライン関数を操作するには、コンパイラの最適化設定も理解する必要があります。

インライン化を使用して、現在のコンパイル ユニット用に既存の関数を置き換えることもできると言う価値があるかもしれません。これは、おおよその答えがアルゴリズムにとって十分である場合、または結果がより高速な方法で得られる場合に使用できます。ローカルデータ構造を使用。

インライン定義は、翻訳者が同じ翻訳単位内の関数への呼び出しを実装するために使用できる外部定義の代替手段を提供します。関数の呼び出しがインライン定義を使用するか、外部定義を使用するかは指定されていません。

一部の関数はインライン化できません

たとえば、インライン化できない GNU コンパイラ関数は次のとおりです。

関数定義での特定の使用法は、インライン置換に適さない場合があることに注意してください。これらの使用法には、可変長関数、alloca の使用、可変長データ型の使用 (可変長を参照)、計算された goto の使用 (値としてのラベルを参照)、非ローカル goto の使用、およびネストされた関数 (ネストされた関数を参照) があります。-Winline を使用すると、インラインでマークされた関数を代入できなかった場合に警告が表示され、失敗の理由が示されます。

そのためalways_inline、期待どおりに動作しない場合もあります。

コンパイラ オプション

C99 のインライン ヒントを使用するには、探しているインライン動作をコンパイラに指示する必要があります。

たとえば、GCCには次のものがあります。

-fno-inline, -finline-small-functions, -findirect-inlining, -finline-functions, -finline-functions-called-once, -fearly-inlining_-finline-limit=n

Microsoft コンパイラには、インラインの有効性を決定するオプションもあります。一部のコンパイラでは、実行中のプロファイルを考慮して最適化することもできます。

プログラム最適化のより広い文脈でインライン化を見る価値があると思います。

インライン化の防止

特定の関数をインライン化したくないとおっしゃいました。__attribute__((always_inline))これは、オプティマイザーをオンにせずに、次のように設定することで実行できます。ただし、おそらくオプティマイザーが必要になるでしょう。ここでの 1 つのオプションは、それを望まないことをほのめかすことです: __attribute__ ((noinline)). しかし、なぜこれが当てはまるのでしょうか?

他の形態の最適化

また、ループを再構築して分岐を回避する方法を検討することもできます。分岐予測は劇的な効果をもたらす可能性があります。これに関する興味深い議論については、ソートされていない配列よりもソートされた配列を処理する方が速いのはなぜですか?

次に、内側のループを小さくして展開し、不変条件を確認することもできます。

于 2015-09-17T10:59:20.857 に答える
3

同じ関数に 2 つの名前を付けてもかまわない場合は、関数の周りに小さなラッパーを作成して、always_inline 属性がすべての呼び出しに影響を与えないように「ブロック」することができます。私の例でloop_inlinedは、パフォーマンスが重要なセクションで使用する名前になりますが、それ以外の場所ではプレーンloopが使用されます。

inline.h

#include <stdlib.h>

static inline int loop_inlined() __attribute__((always_inline));
int loop();

static inline int loop_inlined() {
    int n = 0, i;
    for(i = 0; i < 10000; i++) 
        n += rand();
    return n;
}

inline.c

#include "inline.h"

int loop() {
    return loop_inlined();
}

main.c

#include "inline.h"
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("%d\n", loop_inlined());
    printf("%d\n", loop());
    return 0;
}

これは、最適化レベルに関係なく機能します。Intelでコンパイルすると、次のようgcc inline.c main.cになります。

4011e6:       c7 44 24 18 00 00 00    movl   $0x0,0x18(%esp)
4011ed:       00
4011ee:       eb 0e                   jmp    4011fe <_main+0x2e>
4011f0:       e8 5b 00 00 00          call   401250 <_rand>
4011f5:       01 44 24 1c             add    %eax,0x1c(%esp)
4011f9:       83 44 24 18 01          addl   $0x1,0x18(%esp)
4011fe:       81 7c 24 18 0f 27 00    cmpl   $0x270f,0x18(%esp)
401205:       00
401206:       7e e8                   jle    4011f0 <_main+0x20>
401208:       8b 44 24 1c             mov    0x1c(%esp),%eax
40120c:       89 44 24 04             mov    %eax,0x4(%esp)
401210:       c7 04 24 60 30 40 00    movl   $0x403060,(%esp)
401217:       e8 2c 00 00 00          call   401248 <_printf>
40121c:       e8 7f ff ff ff          call   4011a0 <_loop>
401221:       89 44 24 04             mov    %eax,0x4(%esp)
401225:       c7 04 24 60 30 40 00    movl   $0x403060,(%esp)
40122c:       e8 17 00 00 00          call   401248 <_printf>

最初の 7 命令はインライン呼び出しで、通常の呼び出しは 5 命令後に発生します。

于 2015-09-21T19:31:01.897 に答える
1

コードの本体を別のヘッダー ファイルに記述することをお勧めします。インラインにする必要がある場所にヘッダー ファイルをインクルードし、他の呼び出しのために C ファイルの本体にインクルードします。

void demo(void)
{
#include myBody.h
}

importantloop
{
    // code
#include myBody.h
    // code
}
于 2015-09-18T15:48:40.283 に答える
-2

あなたの関数はインライン化したいので小さなものだと思います。もしそうなら、asmで書いてみませんか?

関数への特定の呼び出しのみをインライン化することに関しては、このタスクを実行する何かが存在するとは思いません。関数がインラインとして宣言されると、コンパイラがそれをインライン化する場合、その関数への呼び出しが見られるすべての場所でインライン化されます。

于 2015-09-17T16:27:38.647 に答える