[解決済み] ヒープにあるデータへのアクセスは、スタックからよりも速いのですか?
質問
これは一般的な質問に聞こえるでしょうし、多くの似たような質問を(ここでもウェブ上でも)見てきましたが、どれも私のジレンマのようなものではありませんでした。
私がこのコードを持っているとします。
void GetSomeData(char* buffer)
{
// put some data in buffer
}
int main()
{
char buffer[1024];
while(1)
{
GetSomeData(buffer);
// do something with the data
}
return 0;
}
buffer[1024]をグローバルに宣言した場合、パフォーマンスは向上するでしょうか?
unixでtimeコマンドを使用していくつかのテストを実行しましたが、実行時間の差はほとんどありませんでした。
しかし、あまり納得がいきません...。
理論的には、この変化で違いが出るはずなのでしょうか?
どのように解決するのですか?
<ブロッククオートヒープ内のデータへのアクセスは、スタックからのアクセスより速いのですか?
私がこれまで取り組んできたすべてのアーキテクチャにおいて、すべてのプロセスのメモリは、現在のデータを保持している CPU キャッシュ/RAM/スワップ ファイルのどのレベルに基づいて、同じ速度で動作することが期待されます。
OS (ページフォルト/スワップを担当) とハードウェア (CPU) は、まだアクセスされていないページやスワップアウトされたページへのアクセスをトラップしますが、どのページが "global" vs "stack" vs "heap" であるかさえ追跡しません... メモリ ページはメモリ ページなのです。
メモリが置かれるグローバル vs スタック vs ヒープの使用法は OS やハードウェアには不明であり、すべて同じパフォーマンス特性を持つ同じタイプのメモリによって支えられていますが、他にも微妙な考慮事項があります (このリストの後で詳しく説明します)。
-
割り当て
- プログラムがメモリの割り当てと割り当て解除に費やす時間。
sbrk
(ヒープ使用量の増加に伴う仮想アドレスの割り当てなど。 - アクセス - グローバルとスタックとヒープにアクセスするためにプログラムが使用する CPU 命令の違い、および、余分な インダイレクト ヒープベースのデータを使用する場合は、ランタイムポインタを介して。
- レイアウト - 特定のデータ構造 ("container" / "collections") はよりキャッシュフレンドリーですが (したがって高速)、いくつかの汎用実装はヒープ割り当てを必要とし、キャッシュフレンドリーでない場合があります。
割り当てと解放
について グローバルデータ (C++ 名前空間データメンバを含む) では、仮想アドレスは通常、計算されて コンパイル時 (絶対値として、またはセグメント レジスタからのオフセットとして。時には、OS によってプロセスがロードされるときに微調整が必要な場合もあります)。
については スタック -ベースのデータでは、スタックポインタ・レジスタ相対アドレスも計算され、以下のようにハードコードされます。 コンパイル時 . それから、スタックポインタレジスタは、関数が入力され、戻るとき(すなわち実行時)、関数の引数、ローカル変数、リターンアドレスと保存されたCPUレジスタの合計サイズによって調整されるかもしれません。 スタックベースの変数を追加すると、スタックポインタレジスタを調整するために使用される合計サイズが変化するだけで、むしろ有害な影響が大きくなります。
ヒープ ベースのオーバーヘッドは非常に現実的で、一部のアプリケーションでは重要かもしれませんが、上記の両方は実行時の割り当て/解放のオーバーヘッドから効果的に解放されます...
のために
ヒープ
-ベースのデータでは
ランタイム
ヒープアロケーションライブラリは、アプリケーションがメモリを解放または削除するまで、管理しているヒープメモリのブロックまたはプールのどの部分が、ライブラリがアプリケーションに提供した特定のポインタと関連しているかを追跡するために、内部データ構造を参照し更新しなければなりません。 ヒープメモリの仮想アドレス空間が足りない場合、OSの機能である
sbrk
を実行して、より多くのメモリを要求します(Linux では
mmap
を呼び出して、大きなメモリ要求のためのバッキングメモリを作成し、そのメモリは
free
/
delete
).
アクセス方法
グローバルおよびスタックベースのデータに対して、コンパイル時に絶対仮想アドレス、またはセグメントまたはスタックポインタ・レジスタ相対アドレスを計算できるため、実行時のアクセスは非常に高速になります。
ヒープホストされたデータでは、プログラムはヒープ上の仮想メモリアドレスを保持する実行時に決定されたポインタを介してデータにアクセスしなければならず、時にはポインタから特定のデータメンバーへのオフセットが実行時に適用されます。 これは、いくつかのアーキテクチャでは少し時間がかかるかもしれません。
ヒープアクセスでは、ポインターとヒープメモリの両方が、データにアクセスするためのレジスタになければなりません (したがって、CPU キャッシュに対する要求が多くなり、規模によってはキャッシュミス/障害オーバーヘッドが多くなります)。
注意: これらのコストは、レイテンシーやスループットが非常に重要であるようなものを書いていない限り、しばしば取るに足らないもので、見たり考えたりする価値さえありません。
レイアウト
ソースコードの連続した行がグローバル変数をリストしている場合、それらは(アライメント目的のパディングはあり得るものの)隣接するメモリ位置に配置されます。 同じ関数にリストされているスタック ベースの変数についても同様です。 X バイトのデータがある場合、N バイトのキャッシュラインでは、X/N または X/N + 1 のキャッシュラインを使ってアクセスできるメモリにうまくパックされることがわかります。 関数の引数やリターンアドレスなど、近くにある他のスタック コンテンツが、プログラムによって同じ時期に必要になる可能性はかなり高いので、キャッシュは非常に効率的です。
ヒープ ベースのメモリを使用する場合、ヒープ割り当てライブラリへの連続した呼び出しは、特に割り当てサイズがかなり異なる場合 (たとえば、3 バイトの割り当てに続いて 13 バイトの割り当て)、またはすでに多くの割り当てと解放が行われている場合 ("fragmentation" を引き起こす)、異なるキャッシュ ラインのメモリへのポインタを簡単に返すことができます。 つまり、ヒープに割り当てられた小さなメモリの束にアクセスしようとすると、最悪の場合、(ヒープへのポインタを含むメモリをロードする必要があるのに加えて)多くのキャッシュラインをフォールトする必要があるかもしれないのです。 ヒープに割り当てられたメモリは、スタックに割り当てられたデータとキャッシュ ラインを共有することはありません。
さらに、C++ 標準ライブラリは、スタックベースのメモリで使用するために設計された、リンクリスト、平衡バイナリツリー、ハッシュテーブルなどのより複雑なデータ構造を提供しません。 そのため、スタックを使用する場合、プログラマは、多少ブルート フォース検索を意味するとしても、メモリ内で連続している配列でできることを行う傾向があります。 キャッシュ効率は、要素がより多くのキャッシュラインに分散しているヒープベースのデータコンテナよりも、全体として優れていると言えるかもしれません。 もちろん、スタックの使用は多数の要素に対してスケールしませんし、少なくともヒープを使用するというバックアップ オプションがなければ、予想以上に処理するデータが与えられると動作しなくなるプログラムを作成します。
サンプル プログラムについての議論
あなたの例では、グローバル変数と関数ローカル(スタック/自動)変数を対比しています...ヒープは関係ありません。 ヒープメモリは
new
または
malloc
/
realloc
. ヒープメモリの場合、注目すべきパフォーマンス上の問題は、アプリケーション自身が、どのアドレスでどれだけのメモリが使用されているかを記録していることです。
new
/
malloc
/
realloc
というように更新され、さらにいくつかのポインタが
delete
または
free
d.
グローバル変数の場合、メモリの割り当ては事実上コンパイル時に行われます。一方スタックベースの変数の場合、通常スタックポインタがあり、関数が呼ばれるたびにコンパイル時に計算されたローカル変数のサイズ(およびいくつかのハウスキーピングデータ)の合計によって増分されます。 そのため
main()
が呼ばれた場合、スタックポインタを修正する時間があるかもしれません。
buffer
がなければ変更されず、あれば変更されるというだけのことで、実行時の性能に違いは全くありません。
ノート
上記では、退屈でほとんど関係のないいくつかの詳細を省略しました。 たとえば、CPU によっては、ある関数が別の関数の呼び出しに入るときに、その状態を保存するためにレジスターのウィンドウを使用します。
関連
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] なぜC++はPythonよりもstdinからの行の読み込みが遅いのですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] <は<=より速いのか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] なぜJavaでは2 * (i * i)の方が2 * i * iより速いのですか?
-
[解決済み] なぜ[]はlist()よりも速いのですか?
-
[解決済み] どちらが速いですか?スタックアロケーションとヒープアロケーション
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】coutはstdのメンバではない
-
[解決済み】C++でユーザー入力を待つ【重複あり
-
[解決済み】関数名の前に期待されるイニシャライザー
-
[解決済み] クラスにデフォルトコンストラクタが存在しない。
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み】#include<iostream>は存在するのですが、「識別子 "cout "は未定義です」というエラーが出ます。なぜですか?
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません