22

私の関数は何千回も呼び出されます。高速化したい場合、ローカル関数変数を static に変更することは役に立ちますか? この背後にある私の論理は、静的変数は関数呼び出し間で永続的であるため、最初にのみ割り当てられるため、後続のすべての呼び出しではメモリが割り当てられず、メモリ割り当てステップが行われないため、高速になるというものです。

また、上記が当てはまる場合、パラメーターの代わりにグローバル変数を使用すると、呼び出されるたびに関数に情報を渡すのが速くなりますか? 再帰を可能にするために、すべての関数呼び出しにパラメーター用のスペースも割り当てられていると思います(そのため、再帰はより多くのメモリを消費します)が、私の関数は再帰的ではないため、私の推論が正しい場合、パラメーターを削除すると理論的にはそれはより速く。

私がやりたいことは、恐ろしいプログラミングの習慣であることはわかっていますが、それが賢明かどうか教えてください。やってみますが、ご意見をお聞かせください。

4

10 に答える 10

25

ローカル変数のオーバーヘッドはゼロです。関数を呼び出すたびに、パラメーター、戻り値などのスタックが既に設定されています。ローカル変数を追加するということは、スタック ポインター (コンパイル時に計算される数値) に少し大きな数値を追加することを意味します。 .

また、キャッシュの局所性により、ローカル変数の方がおそらく高速です。

関数を「数千回」(数百万回または数十億回ではなく) しか呼び出していない場合は、プロファイラーを実行した後に最適化の機会についてアルゴリズムを調べる必要があります。


Re: キャッシュの局所性 (詳細はこちら): 頻繁にアクセスされるグローバル変数には、おそらく一時的な局所性があります。また、関数の実行中にレジスタにコピーされることもありますが、関数が戻った後にメモリ (キャッシュ) に書き戻されます (そうしないと、他のものからアクセスできなくなります。レジスタにはアドレスがありません)。

ローカル変数は通常、時間的局所性と空間的局所性の両方を持ちます (それらはスタック上に作成されることでそれを取得します)。さらに、それらはレジスタに直接「割り当て」られ、メモリに書き込まれることはありません。

于 2010-10-01T01:12:23.573 に答える
12

確認する最善の方法は、実際にプロファイラーを実行することです。これは、両方の方法を使用していくつかの時間指定テストを実行し、結果を平均して比較するのと同じくらい簡単です。または、プロセスにアタッチして、経時的なメモリ使用量と実行速度をグラフ化する本格的なプロファイリング ツールを検討することもできます。

ランダムなマイクロ コード チューニングは実行しないでください。コンパイラーはすべて実装がわずかに異なり、ある環境の 1 つのコンパイラーに当てはまっていても、別の構成では当てはまらない場合があります。

より少ないパラメーターに関するコメントに対処するには、関数を「インライン化」するプロセスにより、関数の呼び出しに関連するオーバーヘッドが本質的に取り除かれます。小さな関数はコンパイラによって自動的にインライン化される可能性がありますが、関数もインライン化することを提案できます。

別の言語である C++ では、登場する新しい標準が完全転送と右辺値参照を使用した完全移動セマンティクスをサポートしています。

時期尚早に最適化しているのではないかと思いますが、実際のボトルネックを発見するまでは、パフォーマンスを気にするべきではありません。

于 2010-10-01T01:06:34.990 に答える
4

絶対にありません!唯一の「パフォーマンス」の違いは、変数が初期化されるときです

    int anint = 42;
 vs
    static int anint = 42;

最初のケースでは、関数が呼び出されるたびに整数が 42 に設定されます。2 番目のケースでは、プログラムがロードされるときに 42 に設定されます。

ただし、違いはごくわずかで、ほとんど目立ちません。呼び出しごとに「自動」変数にストレージを割り当てる必要があるというよくある誤解です。これは、C がこれらの変数のためにスタック内の既に割り当てられたスペースを使用するということではありません。

静的変数では積極的な最適化ができないため、実際には静的変数を使用すると速度が低下する可能性があります。また、ローカルはスタックの連続した領域にあるため、効率的にキャッシュしやすくなります。

于 2010-10-01T01:08:37.623 に答える
3

これに対する答えはありません。これは、CPU、コンパイラ、コンパイラ フラグ、ローカル変数の数、関数を呼び出す前に CPU が行っていたこと、そしておそらく月の満ち欠けによって異なります。

2 つの極端な例を考えてみましょう。ローカル変数が 1 つまたは少数しかない場合は、メモリ ロケーションが割り当てられるのではなく、簡単にレジスタに格納される可能性があります。レジスタの「圧力」が十分に低い場合、命令をまったく実行せずにこれが発生する可能性があります。

反対に、まったくスタックを持たないマシン (IBM メインフレームなど) がいくつかあります。この場合、通常はスタック フレームと考えられるものが、実際にはヒープ上のリンク リストとして割り当てられます。ご想像のとおり、これは非常に遅くなる可能性があります。

変数へのアクセスに関しては、状況はいくぶん似ています。マシン レジスタへのアクセスは、メモリに割り当てられたものよりも高速であることがかなり保証されています。OTOH、スタック上の変数へのアクセスがかなり遅くなる可能性があります。通常、インデックス付きの間接アクセスのようなものが必要であり、(特に古い CPU では) かなり遅くなる傾向があります。OTOH、グローバルへのアクセス(名前はグローバルに表示されませんが、静的です)は通常、絶対アドレスを形成する必要があり、一部のCPUはある程度ペナルティを課します。

結論: あなたのコードをプロファイリングするためのアドバイスでさえ見当違いかもしれません -- 違いは非常に小さいため、プロファイラーでさえ確実に検出できない可能性があり、それを確認する唯一の方法は生成されたアセンブリ言語を調べることです数年かけてアセンブリ言語を十分に学習し、実際に見たときに何かを言うことができるようにします)。これの反対側は、確実に測定することさえできない違いに対処している場合、それが実際のコードの速度に重大な影響を与える可能性は非常に低いため、おそらく問題に値しないということです.

于 2010-10-01T01:50:05.560 に答える
2

静的変数を使用すると、関数が少し速くなる場合があります。ただし、プログラムをマルチスレッドにしたい場合、これは問題を引き起こします。静的変数は関数呼び出し間で共有されるため、異なるスレッドで関数を同時に呼び出すと、未定義の動作が発生します。マルチスレッドは、コードを実際に高速化するために将来やりたいと思うかもしれないタイプのことです。

あなたが言及したことのほとんどは、マイクロ最適化と呼ばれます。一般的に、この種のことを心配するのは悪い考えです。コードが読みにくくなり、保守が難しくなります。また、バグが発生する可能性が非常に高くなります。より高いレベルで最適化を行うことで、費用対効果が高くなる可能性があります。

M2tM が示唆するように、プロファイラーを実行することも良い考えです。非常に使いやすいgprofをチェックしてください。

于 2010-10-01T01:10:05.687 に答える
2

静的と非静的は完全にカバーされているようですが、グローバル変数のトピックについてです。多くの場合、これらはプログラムの実行を高速化するのではなく、遅くします。

その理由は、厳密にスコープ化された変数により、コンパイラーが大幅に最適化することが容易になるためです。コンパイラーがアプリケーション全体を調べて、グローバルが使用される可能性のあるインスタンスを探す必要がある場合、その最適化はそれほど適切ではありません。

これは、ポインターを導入すると複雑になります。たとえば、次のコードがあるとします。

int myFunction()
{
    SomeStruct *A, *B;
    FillOutSomeStruct(B);
    memcpy(A, B, sizeof(A);
    return A.result;
}

コンパイラーは、ポインター A と B がオーバーラップできないことを認識しているため、コピーを最適化できます。A と B がグローバルである場合、オーバーラップまたは同一のメモリを指している可能性があります。これは、コンパイラが「安全にプレイ」する必要があることを意味します。これは遅くなります。この問題は一般に「ポインタ エイリアシング」と呼ばれ、メモリ コピーだけでなく、さまざまな状況で発生する可能性があります。

http://en.wikipedia.org/wiki/Pointer_alias

于 2010-10-01T01:52:20.797 に答える
1

何が最速かを真に判断するために、いつでもアプリケーションの時間を計ることができます。これが私が理解していることです:(これはすべて、プロセッサのアーキテクチャに依存します)

C 関数はスタック フレームを作成します。スタック フレームには、渡されたパラメーターが配置され、ローカル変数が配置され、呼び出し元が関数を呼び出した場所へのリターン ポインターが配置されます。ここにはメモリ管理の割り当てはありません。それは通常、単純なポインターの動きであり、それだけです。スタック外のデータへのアクセスも非常に高速です。通常、ポインタを扱っているときはペナルティが発生します。

グローバル変数または静的変数に関しては、それらは同じです...メモリの同じ領域に割り当てられるという観点から。これらへのアクセスは、ローカル変数とは異なるアクセス方法を使用する場合があり、コンパイラによって異なります。

シナリオ間の主な違いはメモリ フットプリントであり、速度はそれほど大きくありません。

于 2010-10-01T01:10:58.150 に答える
1

静的変数を使用すると、実際にはコードが大幅に遅くなる可能性があります。静的変数は、メモリの「データ」領域に存在する必要があります。その変数を使用するには、関数はロード命令を実行してメイン メモリから読み取るか、ストア命令を実行してメイン メモリに書き込む必要があります。その領域がキャッシュにない場合、多くのサイクルが失われます。スタック上に存在するローカル変数は、ほとんどの場合、キャッシュ内のアドレスを持ち、CPU レジスタ内にある可能性もあり、メモリにはまったく表示されません。

于 2010-10-01T01:46:56.050 に答える
0

プロファイリングでは違いがわからず、逆アセンブルして、何を探すべきかを知っている可能性があります。

ループごとに数クロックサイクル程度の変動しか得られないと思います(平均して、コンパイラなどに依存します)。場合によっては、変更が劇的に改善されたり、劇的に遅くなったりすることがありますが、それは必ずしも変数のホームがスタックとの間で移動したためではありません。2 GHz プロセッサで 10000 回の呼び出しを行う場合、関数呼び出しごとに 4 クロック サイクルを節約するとします。非常に大まかな計算: 20 マイクロ秒の節約。現在の実行時間と比較して、20 マイクロ秒は多いですか、それとも少ないですか?

とりわけ、すべての char 変数と short 変数を int にすることで、パフォーマンスがさらに向上する可能性があります。マイクロ最適化は知っておくとよいことですが、実験、逆アセンブル、コードの実行タイミングの調整には多くの時間がかかります。たとえば、命令が少ないからといって必ずしも高速になるとは限らないことを理解する必要があります。

特定のプログラムを取り上げて、問題の関数とそれを呼び出すコードの両方を逆アセンブルします。静電気の有無にかかわらず。1 つまたは 2 つの命令しか得られず、これが実行する唯一の最適化である場合、その価値はおそらくありません。プロファイリング中は違いを確認できない場合があります。たとえば、キャッシュラインがヒットする場所の変更は、コードの変更前にプロファイリングに表示される可能性があります。

于 2010-10-01T05:03:20.737 に答える
0

そのようなものを見つけるためのプロファイリングに関する他のコメントに同意しますが、一般的に言えば、関数の静的変数は遅くなるはずです。あなたがそれらを望むなら、あなたが本当に求めているのはグローバルです。関数の静的は、コード/データを挿入して、関数が呼び出されるたびに実行されるものが既に初期化されているかどうかを確認します。

于 2010-10-01T01:37:31.233 に答える