4

私が受講したクラスのトレーニング教材は、2 つの相反する内容を述べているようです。

一方では:

「インライン関数を使用すると、通常、実行が高速になります」

一方で:

「インライン関数を使用すると、スワッピングが頻繁になるため、パフォーマンスが低下する可能性があります」

質問 1:両方のステートメントは正しいですか?

質問 2:ここでの「交換」とは何を意味しますか?

このスニペットを見てください:

int powA(int a, int b) {
  return (a + b)*(a + b) ;
}

inline int powB(int a, int b) {
  return (a + b)*(a + b) ;
}

int main () {
    Timer *t = new Timer;

    for(int a = 0; a < 9000; ++a) {
        for(int b = 0; b < 9000; ++b) {
             int i = (a + b)*(a + b);       //              322 ms   <-----
            //  int i = powA(a, b);         // not inline : 450 ms
            //  int i = powB(a, b);         // inline :     469 ms
        }
    }

    double d = t->ms();
    cout << "-->  " << d << endl; 

    return 0;
}

質問 3:powAと の間でパフォーマンスが非常に似ているのはなぜpowBですか? powB結局のところ、インラインであるため、パフォーマンスは 322 ミリ秒になると予想していました。

4

6 に答える 6

5

質問1

はい、特定の状況では、両方のステートメントが当てはまる可能性があります。明らかに、両方が同時に真になるわけではありません。

質問2

「スワッピング」は、メモリの負荷が高くなるとページがディスクにスワップアウトされるOSページング動作を指している可能性があります。

実際には、インライン関数が小さい場合は、通常、関数呼び出しと戻りのオーバーヘッドがなくなるため、パフォーマンスが向上します。ただし、非常にまれな状況では、コードが大きくなり、CPUキャッシュ内に完全に常駐できなくなり(パフォーマンスが重要なタイトループ中)、パフォーマンスが低下する場合があります。ただし、そのレベルでコーディングしている場合は、とにかくアセンブリ言語で直接コーディングする必要があります。

質問3

inline修飾子は、指定された関数をインラインでコンパイルすることを検討する可能性があるというコンパイラーへのヒントです。それはあなたの指示に従う必要はありません、そして結果はまた与えられたコンパイラオプションに依存するかもしれません。生成されたアセンブリコードをいつでも見て、それが何をしたかを知ることができます。

コンパイラは、割り当てた関数呼び出しの結果を使用していないことを確認できるほど賢いため、ベンチマークが期待どおりに機能していない可能性があります。そのため、関数iを呼び出す必要がない場合もあります。もう一度、生成されたアセンブリコードを見てください。

于 2012-08-13T01:54:52.010 に答える
4

inline呼び出しサイトにコードを挿入し、スタックフレームの作成、レジスタの保存/復元、および呼び出し(ブランチ)を節約します。言い換えると、inline(動作する場合に)使用することは、呼び出しの代わりにインライン関数のコードを記述することに似ています。

ただし、inline何もすることが保証されているわけではなく、コンパイラに依存します。コンパイラーはinlineインラインではない関数を実行することがあります(リンク時の最適化がオンになっている場合、おそらくリンカーがそれを実行しますが、コンパイラーレベルで実行できる状況を想像するのは簡単です-たとえば、インライン関数が静的)。

MSVCを強制的にinline機能させる場合__forceinlineは、アセンブリを使用して確認します。呼び出しはありません。コードは、線形に実行される命令の単​​純なシーケンスにコンパイルする必要があります。

速度について:小さな関数をインライン化することで、実際にコードを高速化できます。ただし、関数が大きい場合inline(および「大きい」を定義するのが難しい場合は、テストを実行して大きいものとそうでないものを判別する必要があります)、コードサイズは大きくなります。これは、インライン関数のコードが呼び出しサイトで何度も繰り返されるためです。結局のところ、関数を呼び出すことの全体的なポイントは、コード内の複数の場所から同じサブルーチンを再利用することによって、命令数を節約することです。

コードサイズが大きくなると、命令キャッシュが圧倒され、コードの実行が遅くなる可能性があります。

考慮すべきもう1つのポイント:最新の故障したCPU(ほとんどのデスクトップCPU-たとえばIntel Core Duoまたはi7)には、inlineハードウェアレベルでブランチをプリフェッチするメカニズム(命令トレース)があります。したがって、積極的なインライン化は必ずしも意味がありません。

あなたの例では、コンパイラが生成するアセンブリを確認する必要があります。inlineバージョンと非バージョンで同じである可能性がありinlineます。そうでない場合は、使用しているのがMSVCであるかどうかinlineを試してください。__forceinlineどちらの場合もタイミングが同じである場合は、CPUが命令のプリフェッチで適切に機能し、実行時間のボトルネックが他の場所にあることを意味します。

于 2012-08-13T02:02:04.253 に答える
1

スワッピングは、実行中のプロセスの内外でメモリのさまざまなページをスワッピングすることに関するOS用語です。基本的にスワップには時間がかかります。アプリが大きいほど、スワッピングが増える可能性があります。

関数をインライン化すると、単一のサブルーチンにジャンプする代わりに、関数全体のコピーが呼び出し元の場所にダンプされます。これによりプログラムが大きくなるため、理論的にはより多くのスワッピングにつながる可能性があります。

通常、非常に小さなメソッド(powAやpowBなど)の場合、インライン化は問題なく実行が速くなりますが、実際には「理論上」です。パフォーマンスの最後の一滴を絞るという点では、おそらく「より大きな魚を揚げる」必要があります。あなたのコードから。

于 2012-08-13T01:56:18.503 に答える
1

本の記述は正しいです。つまり、適切にinline実行するとパフォーマンスが向上し、不適切に実行するとパフォーマンスが低下する可能性があります。

小さな関数のみをインライン化することをお勧めします。これにより、メモリにジャンプするための追加のアセンブリ呼び出しが減ります。これにより、パフォーマンスが向上します。

大規模な関数の場合inline、これによりメモリページングがキャッシュサイズを超え、追加のメモリスワッピングが発生する可能性があります。これがパフォーマンスの妨げになります。

于 2012-08-13T01:56:20.743 に答える
1

どちらのステートメントも正しいです。関数の宣言inlineは、可能であればインライン化するためのコンパイラーへのインジケーターです。コンパイラーは(通常)実際にインライン化するかどうかについて独自の判断を使用しますが、C ++で宣言するとinline、少なくともシンボル生成ではコード生成が変更されます。

このコンテキストでの「スワッピング」とは、実行可能イメージをディスクにページングすることを指します。実行可能ファイルは大きいため、メモリに制約のあるシステムのパフォーマンスに影響を与える可能性があります。

3番目の質問に答えると、コンパイラーは両方の関数に対して同じ動作(私の推測では非インライン)を選択しました。

于 2012-08-13T01:57:50.267 に答える
1

通常の関数をコンパイルすると、そのマシンコードは一度コンパイルされ、それを呼び出す他の関数とは別の場所に配置されます。コードを実行するとき、プロセッサはコードが格納されている場所にジャンプする必要があり、このjump命令はメモリから関数をロードするために余分な時間を要します。場合によっては、仮想関数などの関数を呼び出すために、いくつかのジャンプ(またはいくつかのロードとジャンプ)が必要になります。レジスタの保存と復元、およびスタックフレームの作成に費やされる時間もありますが、これらは十分に小さいインライン関数には実際には必要ありません。

インライン関数がコンパイルされると、そのすべてのマシンコードが呼び出された場所に直接挿入されるため、jump命令は削除されます。コンパイラはまた、その周囲に基づいてインライン関数のコードを最適化します(たとえば、レジ​​スタの割り当てでは、関数の外部と関数の内部で使用される変数の両方を考慮して、保存する必要のあるレジスタの数を最小限に抑えることができます)。ただし、インライン関数のコードは、呼び出し元の関数の複数の場所に表示される場合があるため(呼び出し元のコードで複数回呼び出された場合)、全体としてコードベースが大きくなります。これにより、コードが大きくなり、CPUキャッシュに収まらなくなる可能性があります。その場合、プロセッサはメインメモリに移動してコードをフェッチする必要があり、キャッシュからすべてを取得するよりも時間がかかります。状況によっては、これにより、jump命令し、コードをインライン化した場合よりもコードを遅くします。

「スワッピング」とは、通常、CPUキャッシュと同じ種類のトレードオフを持つ仮想メモリの動作を指しますが、ディスクからコードをロードするのにかかる時間ははるかに長く、プログラムがこれのために満たす必要のあるメモリの量です。場に出るのははるかに大きいです。インライン関数が仮想メモリのパフォーマンスに影響を与えることはほとんどありません。

明らかに、両方の効果が同時に発生するわけではありませんが、特定の状況でどちらが適用されるかを知ることは困難です。

于 2012-08-13T01:59:56.600 に答える