スタックですべてをより効率的に行うことができるのに、なぜヒープが必要なのですか?


24

これは実際、昨日私が今日使用するアプリケーションでスタックヒープの両方が必要な理由について質問したことと、単純な&通過する単一の標準)。

ただし、応答の多くは、ヒープの割り当て/参照を試みるよりも数百(または数千)倍高速であるため、スタックはかけがえのないものであることを示しました。ヒープを廃止すると動的ストレージ割り当てに問題があることはわかっていますが、これを回避する方法はありませんか、またはおそらくスタックを改善して動的メモリ割り当てを処理できるようにする方法はありませんか?


4
前の質問からの2つの抜粋:「最も重要な欠点は、スペースが限られているため、その中に大きなオブジェクトを保持するか、長寿命のオブジェクトに使用しようとするのは悪い考えです」と「スタックは非常に効率的です」 LIFO(後入れ先出し)ルールに従うデータを管理するための構造」。
カスカベル

2
あなたの前提は不完全です- スタック上ですべてがより効率的に実行できるわけではありません。これは、あなたが受け取った答えに矛盾はありません-どのようなことをすることができ、スタック上で行うことがはるかに高速が行うことができます。
インゴ

...ハードウェアにスタックまたはスタック相対アドレス指定があると仮定します。
リッチメルトン

3
私は確信しています。私はそれを行うと言います。
-JeffO

回答:


25

スタックの問題は、スタックの一番上にない限り、メモリを「解放」できないことです。たとえば、さまざまなサイズの3つのものを割り当てたとします。

a = allocate(2000000); // 2000000 bytes
b = allocate(1);
c = allocate(5000000);

スタックはa、下部、b中央、c上部にあります。解放したい場合、これは問題になりbます:

free(b); // b is not on top! We have to wait until c is freed!

回避策は、すべてのデータを後に移動し、bシフトする場合は後に移動することですa。これは機能しますが、この場合5000000コピーが必要になります。これは、ヒープよりもはるかに遅いものです。

これがヒープがある理由です。割り当てはスタック(O(log n)vs O(1))よりも遅い場合がありますが、ヒープを使用するO(log n)と、スタックのO(n)


4
これに関連するのは、Facebookが削除を要求したときにFacebookがディスクからコンテンツを削除しないことであり、Facebookはそのポインタを削除するだけです。明らかに、デフラグを試みるか、ディスク上の同等のギャップを見つけようとするオーバーヘッドは、データを書き込む速度では時間がかかりすぎるため、ディスクの最高水準点にすべてを追加するだけです。
ポール

まあ、facebookのディスクはヒープとして見ることができます。そして、それらのディスク用のガベージコレクションがあると確信しています。
deadalnix

2
@deadalnix実際には、これは通常、より多くのメモリに使用されるヒープの代わりに巨大なスタックを使用する例です。ただし、Facebookは特別なケースです。データは削除されるよりもはるかに高速に追加されるため、割り当て解除によって成長率に大きな違いが生じることはありません。O(1)割り当てを取得するために、意図的にメモリリークをデザインに含めることができます。
トムクラークソン

7
FBは、コンテンツがそう、彼らは自分の利益のために私のそれことができるということです削除しない主な理由... @PaulTomblin
quant_dev

5
Facebook doesn't remove content from its disk when you ask it to remove it, it just removes the pointer to it-これは、基本的に、任意のオペレーティングシステムでファイルの通常の削除を行ったときに起こることです。
ロバートハーベイ

5

スタックはスレッドごと、ヒープはプロセス全体

100個のスレッドですべての作業項目を処理している場合、キューに入れると、100個のスレッドのいずれかがそれらを見ることができるように、作業項目を正確にどこに割り当てますか?

他の種類のメモリもあります

たとえば、メモリマップファイル、共有メモリ、I / Oマップ(カーネルモード)。効率性の議論は、これらの状況ではあまり意味がありません。


4

スタックはLIFO(後入れ先出し)構造であり、その上部には参照ポインターが保持されます(通常はハードウェアでサポートされます)。これを言っても、ヒープの代わりにスタックに割り当てようとするものはすべて、このスタックの最上部にあるすべての関数のローカル変数である必要があります。したがって、スタックに対する主な理由は、すべてのデータ構造が割り当てられる前に、main()ルーチンが、プログラムが使用するすべてのデータ構造(プログラムの全期間にわたって使用することを目的とする)を事前に割り当てる必要があることです関数呼び出し内で、それらの関数呼び出しが戻り、フレームまたはアクティベーションレコードがスタックからポップオフされると、最終的に削除されます。


3
FIFOではなくLIFO。
Pubby

FILOと呼ばれることもあります;)
エノン

3

スタックは、先入れ先出し(LIFO)規則に従うメモリ割り当てに最適です。つまり、メモリを割り当てるのとまったく逆の順序でメモリを解放します。LIFOは非常に一般的で、おそらく最も一般的なメモリ割り当てパターンです。しかし、それは唯一のパターンではなく、唯一の一般的なパターンでもありません。さまざまな問題に対処できる効率的なプログラムを作成するには、より複雑なインフラストラクチャを意味する場合でも、あまり一般的でないパターンを考慮に入れる必要があります。

段落のすべてのメタを取得できる場合:あなたは初心者であり、初心者としてシンプルさと黒と白のルールを大切にします。ただし、初心者としては、コンピュータープログラムが対応しなければならない問題や制約の範囲を覗き見することしかできません。あなたは、75年もの間、活発に開発されてきた技術に参入しています。なぜ物事がそうであるのかを尋ねることに何の問題もありませんが、答えは一般に「ええ、私たちは50年前に単純で簡単な方法を試しました。問題があるため、もっと複雑なことをしなければなりませんでした」。技術が進歩するにつれて、一般的に効率性と柔軟性に道を譲る必要があります。


0

別の例として、クロージャー。ポップをスタックすると、クロージャーのアクティベーションレコードが失われる可能性があります。したがって、その匿名関数とそのデータを保持したい場合は、ランタイムスタック以外の場所に保存する必要があります。


0

ヒープストレージを割り当てる他の多くの理由の中で。

呼び出し元のプログラムにオブジェクトのアドレスを返したい場合は、スタックストレージが再利用され、呼び出された次の関数によって上書きされる可能性があるため、スタック変数のアドレスを渡さないでください。malloc()を使用して必要なストレージを取得し、後続の関数の呼び出しによって上書きされないようにする必要があります。

スタック項目のアドレスを関数から呼び出す関数に渡すことができます。これは、プログラムが「returns()」まで存在することを保証できるためです。ただし、関数が戻るとすぐに、すべてのスタックストレージが利用可能になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.