41

さて、関数を呼び出すオーバーヘッドがないためだと思いますが、関数を呼び出すオーバーヘッドは本当にそれほど重いのですか (そして、インライン化することの価値があります) ?

私が覚えている限りでは、f(x,y) などの関数が呼び出されると、x と y がスタックにプッシュされ、スタック ポインターが空のブロックにジャンプして実行が開始されます。これは少し単純化しすぎていることは承知していますが、何か不足していますか? 関数を呼び出すための数回のプッシュとジャンプ、本当にそれほど多くのオーバーヘッドがありますか?

忘れ物があれば教えてください、ありがとう!

4

16 に答える 16

63

呼び出しがない(したがって、呼び出し前のパラメーターの準備や呼び出し後のクリーンアップなどの関連費用がない)という事実に加えて、インライン化のもう1つの重要な利点があります。関数本体がインライン化されると、その本体は呼び出し元の特定のコンテキストで再解釈できます。これにより、コンパイラーはコードをさらに削減および最適化できる可能性があります。

簡単な例として、この関数

void foo(bool b) {
  if (b) {
    // something
  }
  else {
    // something else
  }
}

インライン関数として呼び出された場合、実際の分岐が必要になります

foo(true);
...
foo(false);

ただし、上記の呼び出しがインライン化されている場合、コンパイラーはすぐに分岐を排除できます。基本的に、上記の場合、インライン化により、コンパイラーは関数の引数をコンパイル時定数として解釈できます(パラメーターがコンパイル時定数の場合)。これは、インライン化されていない関数では一般的に不可能です。

しかし、それはリモートでさえそれに限定されません。一般に、インライン化によって可能になる最適化の機会は、はるかに広範囲に及びます。別の例では、関数本体が特定の呼び出し元のコンテキストにインライン化されると、コンパイラーは通常、呼び出し元のコードに存在する既知のエイリアシング関連の関係をインライン化された関数コードに伝播できるため、最適化が可能になります。関数のコードが改善されました。

繰り返しになりますが、考えられる例は多数あります。これらはすべて、インライン化された呼び出しが特定の呼び出し元のコンテキストに没頭しているという基本的な事実に由来しているため、インライン化されていない呼び出しでは不可能なさまざまなコンテキスト間の最適化が可能になります。インライン化を使用すると、基本的に元の関数の多くの個別のバージョンを取得できます。各バージョンは、特定の呼び出し元のコンテキストごとに個別に調整および最適化されます。その代償は、明らかにコードの膨張の潜在的な危険性ですが、正しく使用すれば、顕著なパフォーマンス上の利点を提供できます。

于 2010-10-25T15:34:36.250 に答える
27

「関数を呼び出すための数回のプッシュとジャンプ、本当にそんなに多くのオーバーヘッドがありますか?」

機能によります。

関数の本体が 1 つの機械語命令だけである場合、呼び出しと戻りのオーバーヘッドは数百 % になる可能性があります。たとえば、6 回、500% のオーバーヘッドです。次に、プログラムがその関数への無数の呼び出しだけで構成されている場合、インライン化がなければ、実行時間が 500% 増加します。

ただし、逆に、インライン化は有害な影響を与える可能性があります。たとえば、インライン化を行わないとメモリの 1 ページに収まるコードが収まらないためです。

したがって、最適化に関しては常に答えは常に、まず第一に測定です。

于 2010-10-25T15:37:31.810 に答える
12

呼び出しとスタックのアクティビティはありません。これにより、CPUサイクルが確実に節約されます。最近のCPUでは、コードの局所性も重要です。呼び出しを行うと、命令パイプラインがフラッシュされ、CPUがメモリのフェッチを待機するように強制される可能性があります。プライマリメモリは最新のCPUよりもかなり遅いため、これはタイトなループでは非常に重要です。

ただし、アプリケーションでコードが数回しか呼び出されない場合は、インライン化について心配する必要はありません。ユーザーが回答を待っている間に何百万回も呼び出されている場合は、非常に心配です。

于 2010-10-25T15:28:36.670 に答える
11

インライン化の古典的な候補は、 のようなアクセサですstd::vector<T>::size()

インライン化を有効にすると、これはメモリから変数をフェッチするだけで、おそらくどのアーキテクチャでも単一の命令になります。「数回のプッシュとジャンプ」(およびリターン)は、簡単に数倍になります。

それに加えて、オプティマイザが一度に認識できるコードが多いほど、より適切に作業できるという事実があります。多くのインライン化により、一度に多くのコードが表示されます。これは、値を CPU レジスタに保持できる可能性があることを意味し、コストのかかるメモリへの移動を完全に回避できます。ここで、数桁の差が生じる可能性があります。

そして、テンプレートのメタプログラミングがあります。これにより、再帰の最後に単一の値を取得するためだけに、多くの小さな関数が再帰的に呼び出されることがあります。(数十のオブジェクトを持つタプルの特定の型の最初のエントリの値を取得することを考えてみてください。) インライン化を有効にすると、オプティマイザはその値 (レジスタにある可能性があることを思い出してください) に直接アクセスでき、数十の関数を折りたたむことができます。 CPU レジスタ内の単一の値へのアクセスを呼び出します。これにより、ひどいパフォーマンス ホッグが素晴らしく高速なプログラムに変わる可能性があります。


オブジェクト内のプライベート データとして状態を隠す (カプセル化) にはコストがかかります。インライン化は、これらの抽象化のコストを最小限に抑えるために、最初から C++ の一部でした。当時のコンパイラは、現在よりも適切なインライン化候補の検出 (および不適切な候補の拒否) が大幅に悪かったため、手動でインライン化すると、速度が大幅に向上しました。
現在、コンパイラはインラインよりもはるかに賢いと言われています。コンパイラは、関数を自動的にインライン化することができます。またはinline、ユーザーが としてマークした関数をインライン化できますが、インライン化しません。インライン展開は完全にコンパイラに任せるべきであり、わざわざ関数をinline. ただし、手動で行う価値があるかどうかを示す包括的な調査はまだ見ていません。ですから当分の間、私はそれを自分でやり続け、コンパイラがそれをより良くできると判断した場合はそれをオーバーライドさせます。

于 2010-10-25T17:53:45.370 に答える
5

させて

int sum(const int &a,const int &b)
{
     return a + b;
}
int a = sum(b,c);

に等しい

int a = b + c

ジャンプなし-オーバーヘッドなし

于 2010-10-25T15:29:16.033 に答える
5

次のような単純な関数を考えてみましょう:

int SimpleFunc (const int X, const int Y)
{
    return (X + 3 * Y); 
}    

int main(int argc, char* argv[])
{
    int Test = SimpleFunc(11, 12);
    return 0;
}

これは、次のコードに変換されます (MSVC++ v6、デバッグ)。

10:   int SimpleFunc (const int X, const int Y)
11:   {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]

12:       return (X + 3 * Y);
00401038   mov         eax,dword ptr [ebp+0Ch]
0040103B   imul        eax,eax,3
0040103E   mov         ecx,dword ptr [ebp+8]
00401041   add         eax,ecx

13:   }
00401043   pop         edi
00401044   pop         esi
00401045   pop         ebx
00401046   mov         esp,ebp
00401048   pop         ebp
00401049   ret

関数本体には 4 つの命令しかありませんが、関数自体を呼び出すための別の 3 つの命令を含まず、関数のオーバーヘッドだけに 15 の命令があることがわかります。すべての命令に同じ時間がかかった場合 (そうではない場合)、このコードの 80% は関数のオーバーヘッドです。

このような単純な関数の場合、関数のオーバーヘッド コードの実行に、メインの関数本体自体と同じくらいの時間がかかる可能性が高くなります。深いループ本体で数百万回/数十億回呼び出される単純な関数がある場合、関数呼び出しのオーバーヘッドが大きくなり始めます。

いつものように、重要なのは、特定の関数をインライン化することで正味のパフォーマンスが向上するかどうかを判断するためのプロファイリング/測定です。「頻繁に」呼び出されないより「複雑な」関数の場合、インライン化によるメリットは計り知れないほど小さい可能性があります。

于 2010-10-25T15:41:51.053 に答える
4

インライン化を高速化する理由は複数ありますが、明らかな理由は1つだけです。

  • ジャンプの指示はありません。
  • ローカリゼーションが向上し、キャッシュの使用率が向上します。
  • コンパイラのオプティマイザが最適化を行う可能性が高くなります。たとえば、レジ​​スタに値が残ります。

キャッシュの使用率も問題になる可能性があります。インライン化によってコードが大きくなると、キャッシュミスが発生する可能性が高くなります。ただし、その可能性ははるかに低くなります。

于 2010-10-25T15:36:20.757 に答える
3

大きな違いを生む典型的な例は、比較関数で O(N log N) である std::sort にあります。

大きなサイズのベクトルを作成し、最初にインライン関数を使用して std::sort を呼び出し、次に非インライン関数を使用して呼び出し、パフォーマンスを測定してみてください。

ちなみに、これは、関数ポインタを必要とする C の qsort よりも、C++ の sort の方が高速な場合です。

于 2010-10-25T15:45:36.410 に答える
2

ジャンプのもう1つの潜在的な副作用は、コードを最初にメモリにロードするか、後でメモリからページアウトするのに十分な頻度で使用されない場合に、ページフォールトをトリガーする可能性があることです。

于 2010-10-25T15:28:08.633 に答える
2

アンドレイの答えはすでにあなたに非常に包括的な説明を与えています。しかし、彼が見逃した1つのポイントを追加するだけで、インライン化は非常に短い関数でも非常に価値があります。

関数本体がほんの数個の命令で構成されている場合、プロローグ/エピローグコード(基本的にはプッシュ/ポップ/呼び出し命令)は、実際には関数本体自体よりも高価になる可能性があります。このような関数を頻繁に呼び出す場合(たとえば、タイトループから)、関数がインライン化されていない限り、CPU時間の大部分を、関数の実際の内容ではなく、関数呼び出しに費やしてしまう可能性があります。

重要なのは、実際には絶対的な条件での関数呼び出しのコストではなく(5クロックサイクルなどかかる場合があります)、関数が呼び出される頻度に比べてどれくらいの時間がかかるかです。関数が10クロックサイクルごとに呼び出すことができるほど短い場合、「不要な」プッシュ/ポップ命令の呼び出しごとに5サイクルを費やすことはかなり悪いことです。

于 2010-10-26T11:16:16.137 に答える
2

(そして、インライン化する価値があります)

インライン化によってコードが大きくなるとは限りません。たとえば、次のような単純なデータ アクセス関数です。

int getData()
{
   return data ;
}

インラインよりも関数呼び出しの方がはるかに多くの命令サイクルが発生するため、そのような関数はインライン化に最適です。

関数本体に大量のコードが含まれている場合、関数呼び出しのオーバーヘッドは実際には取るに足らないものであり、多くの場所から呼び出された場合、実際にはコードが肥大化する可能性があります - ただし、コンパイラはインライン ディレクティブを単純に無視する可能性があります。そのような場合。

電話をかける頻度も考慮する必要があります。大規模なコード本体であっても、関数が 1 つの場所から頻繁に呼び出される場合は、場合によっては節約する価値があります。それは、呼び出しオーバーヘッドとコード本体のサイズの比率、および使用頻度に帰着します。

もちろん、コンパイラに決定を任せることもできます。追加の関数呼び出しを含まない単一のステートメントで構成される関数のみを明示的にインライン化します。これは、パフォーマンスよりもクラスメソッドの開発速度のためです。

于 2010-10-25T18:17:49.727 に答える
1

電話がないからです。機能コードがコピーされました

于 2010-10-25T15:24:55.417 に答える
1

関数のインライン化は、関数呼び出しを定義に置き換えるためのコンパイラーへの提案です。置き換えられた場合、スタック操作[push、pop]を呼び出す関数はありません。しかし、常に保証されるわけではありません。:)

- 乾杯

于 2010-10-25T15:37:28.070 に答える
1

最適化コンパイラは一連のヒューリスティックを適用して、インライン化が有益かどうかを判断します。

関数呼び出しの欠如による利益が、余分なコードの潜在的なコストを上回る場合もあれば、そうでない場合もあります。

于 2010-10-25T15:44:38.523 に答える
0

関数が複数回呼び出される場合、インライン化は大きな違いを生みます。

于 2010-10-25T15:27:40.007 に答える
-1

ジャンプが行われないため。

于 2010-10-25T15:25:42.287 に答える