スタックメモリサイズがそれほど制限されているのはなぜですか?


85

ヒープにメモリを割り当てる場合、唯一の制限は空きRAM(または仮想メモリ)です。それはメモリのGbになります。

では、なぜスタックサイズがそれほど制限されているのですか(約1 Mb)?スタック上に本当に大きなオブジェクトを作成することを妨げる技術的な理由は何ですか?

更新:私の意図が明確でない可能性があります。スタックに巨大なオブジェクトを割り当てたくないので、より大きなスタックは必要ありません。この質問は純粋な好奇心です。


ヒープ上に大きなオブジェクトを作成することが実際的であるのはなぜですか?(通常、コールチェーンはスタックに配置されます。)

4
本当の答えは、ほとんどの答えが描くよりも単純だと思います。「それは私たちがいつもやってきた方法であり、これまでのところ大丈夫だったので、なぜ変更するのですか?」
ジェリーコフィン2012年

@JerryCoffinこれまでに投稿された回答を読んだことがありますか?この質問には、より多くの洞察があります。
user1202136 2012年

3
@ user1202136:私はそれらすべてを読みました-しかし人々は推測しています、そして私の推測では、彼らが引用している要因の多くはおそらく主題に関する最初の決定をする際に考慮されていなかったと思います。フレーズを作るために、「時々葉巻は葉巻だけです」。
ジェリーコフィン2012年

3
「デフォルトのスタックをどのくらいの大きさにする必要がありますか?」「ああ、わからない、いくつのスレッドを実行できるのか?」「Kのどこかで爆発します」「OK、それでは2Kと呼びます。2ギガの仮想があるので、1メガはどうですか?」「ええ、わかりました、次の問題は何ですか?」
マーティンジェームズ

回答:


46

私の直感は次のとおりです。スタックの管理はヒープほど簡単ではありません。スタックは、連続したメモリ位置に格納する必要があります。つまり、必要に応じてスタックをランダムに割り当てることはできませんが、少なくともその目的のために仮想アドレスを予約する必要があります。予約された仮想アドレス空間のサイズが大きいほど、作成できるスレッドは少なくなります。

たとえば、32ビットアプリケーションの仮想アドレス空間は通常2GBです。これは、スタックサイズが2MB(pthreadのデフォルト)の場合、最大1024のスレッドを作成できることを意味します。これは、Webサーバーなどのアプリケーションでは小さい場合があります。スタックサイズをたとえば100MBに増やすと(つまり、100MBを予約しますが、必ずしもすぐに100MBをスタックに割り当てる必要はありません)、スレッド数は約20に制限され、単純なGUIアプリケーションでも制限される可能性があります。

興味深い質問は、64ビットプラットフォームでまだこの制限があるのはなぜかということです。答えはわかりませんが、人々はすでにいくつかの「スタックのベストプラクティス」に慣れていると思います。ヒープに巨大なオブジェクトを割り当てるように注意し、必要に応じて手動でスタックサイズを増やします。したがって、64ビットプラットフォームに「巨大な」スタックサポートを追加することが有用であるとは誰も考えていませんでした。


多くの64ビットマシンには48ビットアドレスしかありません(32ビットよりも大きなゲインが得られますが、それでも制限されます)。追加のスペースがある場合でも、ページテーブルに関する予約方法について心配する必要があります。つまり、スペースを増やすことには常にオーバーヘッドがあります。スレッドごとに巨大なスタックスペースを予約する代わりに、新しいセグメント(mmap)を割り当てる方が、安くはないにしても、おそらく同じくらい安いでしょう。
edA-qa mort-ora-y 2012

4
@ edA-qamort-ora-y:この回答は、割り当てについてではなく、ほとんど無料で、mmapよりもはるかに高速な仮想メモリの予約についてです。
Mooing Duck 2012

33

まだ誰も言及していない1つの側面:

制限されたスタックサイズは、エラー検出および封じ込めメカニズムです。

一般に、CおよびC ++でのスタックの主な仕事は、呼び出しスタックとローカル変数を追跡することであり、スタックが範囲を超えて大きくなると、ほとんどの場合、アプリケーションの設計や動作のエラーになります。 。

スタックが任意に大きくなることが許可されている場合、これらのエラー(無限再帰など)は、オペレーティングシステムのリソースが使い果たされた後でのみ、非常に遅くキャッチされます。これは、スタックサイズに任意の制限を設定することで防止されます。実際のサイズは、システムの劣化を防ぐのに十分小さいことを除けば、それほど重要ではありません。


割り当てられたオブジェクトでも同様の問題が発生する可能性があります(再帰を置き換える方法の1つは、スタックを手動で処理することです)。その制限により、他の方法(より安全/単純/ ..である必要はありません)を使用する必要があります(std::unique_ptrデストラクタを作成するための(おもちゃの)リストの実装に関するコメントの数に注意してください(スマートポインターに依存しません))。
jarod 4220年

15

これは単なるデフォルトサイズです。さらに必要な場合は、より多くを取得できます。ほとんどの場合、リンカーに追加のスタックスペースを割り当てるように指示します。

大きなスタックを持つことの欠点は、多数のスレッドを作成する場合、それぞれに1つのスタックが必要になることです。すべてのスタックがマルチMBを割り当てているが、それを使用していない場合、スペースは無駄になります。

プログラムの適切なバランスを見つける必要があります。


@BJovkeのような一部の人々は、仮想メモリは本質的に無料であると信じています。すべての仮想メモリをバックアップする物理メモリを用意する必要がないのは事実です。少なくとも仮想メモリにアドレスを与えることができなければなりません。

ただし、一般的な32ビットPCでは、仮想メモリのサイズは物理メモリのサイズと同じです。仮想かどうかに関係なく、どのアドレスにも32ビットしかないためです。

プロセス内のすべてのスレッドは同じアドレス空間を共有するため、スレッド間で分割する必要があります。そして、オペレーティングシステムがその役割を果たした後、アプリケーション用に残っているのは「たった」2〜3GBです。そして、そのサイズは、物理メモリ仮想メモリの両方の制限です。これは、アドレスがもうないためです。


最大のスレッドの問題は、スタックオブジェクトを他のスレッドに簡単に通知できないことです。プロデューサースレッドは、コンシューマースレッドがオブジェクトを解放するのを同期的に待機するか、高価で競合を生成するディープコピーを作成する必要があります。
マーティンジェームズ

2
@MartinJames:すべてのオブジェクトをスタックに配置する必要があるとは誰も言っていません。デフォルトのスタックサイズが小さい理由について説明しています。
Mooing Duck 2012

スペースが無駄になることはありません。スタックサイズは、連続した仮想アドレススペースの予約にすぎません。したがって、スタックサイズを100 MBに設定した場合、実際に使用されるRAMの量は、スレッドのスタック消費量によって異なります。
bJovke 2017

1
@ BJovke-しかし、仮想アドレス空間はまだ使い果たされています。32ビットプロセスでは、これは数GBに制限されているため、20 * 100MBを予約するだけで問題が発生します。
ボー・パーソン2017

7

一つには、スタックは連続的であるため、12MBを割り当てる場合、作成したものを下回るには、12MBを削除する必要があります。また、オブジェクトを移動するのがはるかに難しくなります。これは、物事を理解しやすくする可能性のある実際の例です。

部屋の周りに箱を積み上げているとしましょう。管理が簡単なもの:

  • あらゆる重量の箱を積み重ねますが、底に何かを置く必要がある場合は、山全体を元に戻す必要があります。アイテムを山から取り出して他の人に渡したい場合は、すべての箱を外して、箱を他の人の山に移動する必要があります(スタックのみ)
  • あなたはすべての箱(本当に小さな箱を除く)を他のものの上に物を積み重ねない特別な場所に置き、それを一枚の紙(ポインター)に置いて紙を置く場所に書き留めます山。他の人に箱を渡す必要がある場合は、山から紙片を渡すか、紙のコピーを渡して、元の紙を山に置いたままにします。(スタック+ヒープ)

これらの2つの例は大まかな一般化であり、類推で明らかに間違っている点がいくつかありますが、両方の場合の利点を理解するのに役立つことを願っています。


@MooingDuckはい。ただし、プログラムの仮想メモリで作業しています。サブルーチンに入った場合、スタックに何かを置いてから、サブルーチンから戻る必要があります。巻き戻す前に、作成したオブジェクトの割り当てを解除するか、移動する必要があります。私が来た場所に戻るためのスタック。
スコットチェンバレン

1
私のコメントは誤解によるものでしたが(そして私はそれを削除しました)、私はまだこの答えに同意しません。スタックの最上位から12MBを削除することは、文字通り1つのオペコードです。基本的に無料です。また、コンパイラは「スタック」ルールをだますことができ、実際に行うことができるので、オブジェクトを巻き戻す前にオブジェクトをコピー/移動して返す必要はありません。ですから、あなたのコメントも間違っていると思います。
Mooing Duck 2012

ええと、通常、12MBの割り当てを解除すると、スタック上の1つのオペコードがヒープ上の100を超えることはそれほど重要ではありません。おそらく、実際に12MBのバッファを処理するノイズレベルを下回っています。コンパイラが途方もなく大きなオブジェクトが返されていることに気付いたときに不正行為をしたい場合(たとえば、呼び出しの前にSPを移動して、オブジェクトスペースを呼び出し元のスタックの一部にする)、それは問題ありませんが、TBH、そのようなオブジェクトを返す開発者(ポインタ/参照ではなく)オブジェクトは、プログラミングがやや困難です。
マーティンジェームズ

@MartinJames:C ++仕様では、関数は通常、データを宛先バッファーに直接入れ、一時バッファーを使用しないと規定されているため、注意すれば、12MBのバッファーを値で返すオーバーヘッドはありません。
Mooing Duck 2012

4

スタックを近いものから遠いものの順に考えてください。レジスタはCPUに近く(高速)、スタックは少し離れており(ただし、まだ比較的近い)、ヒープは遠くにあります(アクセスが遅い)。

スタックはもちろんヒープ上に存在しますが、それでも継続的に使用されているため、おそらくCPUキャッシュを離れることはなく、平均的なヒープアクセスよりも高速になります。これが、スタックを適切なサイズに保つ理由です。可能な限りキャッシュしておくためです。大きなスタックオブジェクトの割り当て(オーバーフローが発生するとスタックのサイズが自動的に変更される可能性があります)は、この原則に反します。

したがって、これは、昔から残っているだけでなく、パフォーマンスの優れたパラダイムです。


4
スタックサイズを人為的に削減するという理由でキャッシングが大きな役割を果たしていると私は信じていますが、「スタックはヒープ上に存在する」という記述を修正する必要があります。スタックとヒープの両方がメモリに(仮想的または物理的に)存在します。
Sebastian Hojas 2014年

「近いまたは遠い」はアクセス速度とどのように関連していますか?
ミンNghĩa

@MinhNghĩaそうですね、RAM内の変数はL2メモリにキャッシュされ、次にL1メモリにキャッシュされ、さらにそれらもレジスタにキャッシュされます。RAMへのアクセスは遅く、L2へのアクセスはより速く、L1へのアクセスはさらに速く、レジスタは最も高速です。OPが意味すると思うのは、スタックに格納されている変数はすぐにアクセスされるはずなので、CPUはスタック変数をそれに近づけるように最善を尽くします。したがって、小さくしたいので、CPUは変数にすばやくアクセスできます。
157239n20年

1

大きなスタックが必要だと思うことの多くは、他の方法で行うことができます。

Sedgewickの「Algorithms」には、再帰を反復に置き換えることにより、QuickSortなどの再帰アルゴリズムから再帰を「削除」する良い例がいくつかあります。実際には、アルゴリズムはまだ再帰的であり、スタックとして存在しますが、ランタイムスタックを使用するのではなく、ヒープに並べ替えスタックを割り当てます。

(私はPascalで与えられたアルゴリズムを備えた第2版を好みます。それは8ドルで使用できました。)

別の見方をすれば、大きなスタックが必要だと思う場合、コードは非効率的です。より少ないスタックを使用するより良い方法があります。


-8

技術的な理由はないと思いますが、スタック上に巨大なスーパーオブジェクトを1つだけ作成したのは奇妙なアプリでしょう。スタックオブジェクトには柔軟性がなく、サイズが大きくなると問題が大きくなります。オブジェクトを破棄せずに戻ることはできず、他のスレッドにキューに入れることもできません。


1
すべてのオブジェクトをスタックに配置する必要があるとは誰も言っていません。デフォルトのスタックサイズが小さい理由について説明しています。
Mooing Duck 2012

小さくはありません!1MBのスタックを使い果たすには、いくつの関数呼び出しを実行する必要がありますか?とにかくデフォルトはリンカで簡単に変更できるので、「ヒープの代わりにスタックを使用する理由」が残ります。
マーティンジェームズ

3
1つの関数呼び出し。 int main() { char buffer[1048576]; } これは非常に一般的な初心者の問題です。確かに簡単な回避策がありますが、なぜスタックサイズを回避する必要があるのでしょうか。
Mooing Duck 2012

まあ、一つには、私は、苦しんでいる関数を呼び出すすべてのスレッドのスタックに12MB(または実際には1MB)のスタック要件を課したくありません。そうは言っても、1MBは少しけちだということに同意する必要があります。私はデフォルトの100MBで満足します。結局のところ、他の開発者がそれを上げるのを止めるものがないのと同じように、私がそれを128Kに下げるのを止めるものは何もありません。
マーティンジェームズ

1
スレッドに12MBのスタックを与えたくないのはなぜですか?その唯一の理由は、スタックが小さいためです。それは再帰的な議論です。
Mooing Duck 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.