15

シングルスレッドプログラムに次のような関数があるとします

void f(some arguments){
    char buffer[32];
    some operations on buffer;
}

f は頻繁に呼び出されるループ内に現れるので、できるだけ高速にしたいと考えています。f が呼び出されるたびにバッファを割り当てる必要があるように見えますが、静的であると宣言すると、これは起こりません。それは正しい理屈ですか?それは無料のスピードアップですか?そして、その事実 (簡単にスピードアップできるという理由だけで) だけで、最適化コンパイラーは既にこのようなことをしてくれますか?

4

11 に答える 11

14

いいえ、無料の高速化ではありません。

まず、割り当ては最初はほとんど自由です (スタック ポインターに 32 を追加するだけで構成されているため)。次に、静的変数が遅くなる理由が少なくとも 2 つあります。

  • キャッシュの局所性が失われます。スタックに割り当てられたデータはすでに CPU キャッシュにあるため、アクセスは非常に安価です。静的データはメモリの別の領域に割り当てられるため、キャッシュされない可能性があり、キャッシュ ミスが発生し、データがメイン メモリからフェッチされるまで数百クロック サイクル待機する必要があります。
  • スレッドセーフを失います。2 つのスレッドが関数を同時に実行すると、一度に 1 つのスレッドのみがコードのそのセクションを実行できるようにロックが設定されていない限り、関数はクラッシュしてバーンします。つまり、複数の CPU コアを持つ利点が失われることになります。

したがって、無料の高速化ではありません。しかし、あなたの場合はより高速である可能性があります(私はそれを疑っていますが)。試してみて、ベンチマークを行い、特定のシナリオで何が最適かを確認してください。

于 2010-09-16T20:24:13.053 に答える
10

スタック上で 32 バイトをインクリメントしても、ほぼすべてのシステムでほとんどコストがかかりません。しかし、あなたはそれをテストする必要があります。静的バージョンとローカル バージョンをベンチマークし、ポスト バックします。

于 2010-09-16T19:27:16.677 に答える
8

ローカル変数にスタックを使用する実装の場合、多くの場合、割り当てには、スタック ポインター (SP) レジスターなどのレジスターを進める (それに値を追加する) ことが含まれます。このタイミングはごくわずかで、通常は 1 命令以下です。

ただし、スタック変数の初期化には少し時間がかかりますが、それほど長くはありません。正確な詳細については、(コンパイラまたはデバッガによって生成された) アセンブリ言語のリストを確認してください。変数の初期化に必要な期間または命令の数について、標準には何もありません。

通常、静的ローカル変数の割り当ては別の方法で処理されます。一般的なアプローチは、これらの変数をグローバル変数と同じ領域に配置することです。通常、この領域のすべての変数は、 を呼び出す前に初期化されますmain()。この場合の割り付けとは、アドレスをレジスタに割り当てるか、領域情報をメモリに格納することです。ここで無駄な実行時間はあまりありません。

動的割り当ては、実行サイクルがバーンされるケースです。しかし、それはあなたの質問の範囲ではありません。

于 2010-09-16T19:34:58.220 に答える
3

staticC++ (C ではなく)のブロックレベル変数は、最初の使用時に初期化されることに注意してください。これは、追加のランタイム チェックのコストが発生することを意味します。ブランチは、パフォーマンスを向上させるどころか悪化させる可能性があります。(しかし、実際には、他の人が言及しているように、プロファイルする必要があります。)

とにかく、特に再入可能性を意図的に犠牲にすることになるので、それだけの価値があるとは思いません。

于 2010-09-16T19:40:29.860 に答える
3

現在の書き方では、割り当てにコストはかかりません。32 バイトがスタックにあります。唯一の実際の作業は、ゼロ初期化する必要があることです。

ここでは、ローカル スタティックスはお勧めできません。すべての呼び出しが同じバッファーを共有するため、高速化されず、関数を複数のスレッドから使​​用できなくなります。ローカルの静的初期化がスレッドセーフであることが保証されていないことは言うまでもありません。

于 2010-09-16T19:32:11.457 に答える
3

この問題に対するより一般的なアプローチは、いくつかのローカル変数を必要とする関数が何度も呼び出される場合、それをクラスにラップし、これらの変数をメンバー関数にすることを検討することです。サイズを動的にする必要があるかどうかを検討してchar buffer[32]くださいstd::vector<char> buffer(requiredSize)。これは、ループを通過するたびに配列を初期化するよりもコストがかかります

class BufferMunger {
public:
   BufferMunger() {};
   void DoFunction(args);
private:
   char buffer[32];
};

BufferMunger m;
for (int i=0; i<1000; i++) {
   m.DoFunction(arg[i]);  // only one allocation of buffer
}

バッファを静的にすることには別の意味があります。つまり、2 つのスレッドが関数を呼び出してバッファ内のデータを同時に上書きする可能性があるため、マルチスレッド アプリケーションでは関数が安全ではなくなります。一方、それBufferMungerを必要とする各スレッドで個別に使用することは安全です。

于 2010-09-16T19:33:03.543 に答える
2

PC 用のコードを書いている場合、どちらの方法でも意味のある速度の利点はほとんどありません。一部の組み込みシステムでは、すべてのローカル変数を避ける方が有利な場合があります。他のシステムでは、ローカル変数の方が高速な場合があります。

前者の例: Z80 では、任意のローカル変数を持つ関数のスタック フレームを設定するコードがかなり長くなりました。さらに、ローカル変数にアクセスするためのコードは、8 ビット命令でのみ使用できる (IX+d) アドレッシング モードの使用に制限されていました。X と Y が両方ともグローバル/静的変数、または両方がローカル変数である場合、ステートメント "X=Y" は次のいずれかとしてアセンブルできます。

; 両方が静的またはグローバルの場合: 6 バイト。32サイクル
  ld HL,(_Y) ; 16サイクル
  ld (_X)、HL ; 16サイクル
; 両方がローカルの場合: 12 バイト。56サイクル
  ld E,(IX+_Y) ; 14サイクル
  ld D,(IX+_Y+1) ; 14サイクル
  ld (IX+_X),D ; 14サイクル
  ld (IX+_X+1),E ; 14サイクル

コードとスタック フレームをセットアップする時間に加えて、100% のコード スペースのペナルティと 75% の時間のペナルティが発生します。

ARM プロセッサでは、アドレス ポインタの +/-2K 内にある変数を 1 つの命令でロードできます。関数のローカル変数の合計が 2K 以下の場合、1 つの命令でアクセスできます。グローバル変数は、格納されている場所に応じて、通常、ロードするために 2 つ以上の命令を必要とします。

于 2010-09-16T19:48:24.510 に答える
1

ほとんどの実際のケースでは、関数が大幅に遅くなります。これは、静的データ セグメントがスタックの近くになく、キャッシュの一貫性が失われ、アクセスしようとするとキャッシュ ミスが発生するためです。ただし、スタックに通常の char[32] を割り当てると、他のすべての必要なデータのすぐ隣にあり、アクセスするのにほとんどコストがかかりません。char のスタックベースの配列の初期化コストは無意味です。

これは、静的には他にも多くの問題があることを無視しています。

静的なサイズの文字バッファーを割り当てることがパフォーマンスの問題であるとプロファイラーが教えてくれないため、実際にコードをプロファイリングしてスローダウンがどこにあるかを確認する必要があります。

于 2010-09-16T19:42:19.153 に答える
1

関数内にローカル自動変数がある場合は、スタック ポインターを調整する必要があります。調整にかかる時間は一定であり、宣言された変数の数によって変わることはありません。関数にローカル自動変数がまったくない場合は、時間を節約できます

静的変数が初期化されている場合、変数が既に初期化されているかどうかを判断するフラグがどこかにあります。フラグのチェックには時間がかかります。あなたの例では、変数は初期化されていないため、この部分は無視できます。

関数が再帰的に呼び出される可能性がある場合、または 2 つの異なるスレッドから呼び出される可能性がある場合は、静的変数を避ける必要があります。

于 2010-09-16T19:35:58.217 に答える
1

gcc を使用すると、スピードアップが見られます。

void f() {
    char buffer[4096];
}

int main() {
    int i;
    for (i = 0; i < 100000000; ++i) {
        f();
    }
}

そして時間:

$ time ./a.out

real    0m0.453s
user    0m0.450s
sys  0m0.010s

バッファを静的に変更:

$ time ./a.out

real    0m0.352s
user    0m0.360s
sys  0m0.000s
于 2010-09-16T19:31:21.003 に答える
1

変数が正確に何をしているか、どのように使用されているかに応じて、スピードアップはほとんど何もありません。(x86 システムでは) 単純な単一の func(sub esp,amount) を使用して同時にすべてのローカル変数にスタック メモリが割り当てられるため、他のスタック変数が 1 つだけあると、利益がなくなります。これに対する唯一の例外は、非常に巨大なバッファーです。この場合、コンパイラーはメモリを割り当てるために _chkstk に固執する可能性があります (ただし、バッファーがそれほど大きい場合は、コードを再評価する必要があります)。コンパイラは、関数がシングル スレッド環境で使用されることを想定できないため、最適化によってスタック メモリを静的メモリに変換できません。

于 2010-09-16T19:32:03.703 に答える