ヒープにメモリを割り当てる場合、唯一の制限は空きRAM(または仮想メモリ)です。それはメモリのGbになります。
では、なぜスタックサイズがそれほど制限されているのですか(約1 Mb)?スタック上に本当に大きなオブジェクトを作成することを妨げる技術的な理由は何ですか?
更新:私の意図が明確でない可能性があります。スタックに巨大なオブジェクトを割り当てたくないので、より大きなスタックは必要ありません。この質問は純粋な好奇心です。
ヒープにメモリを割り当てる場合、唯一の制限は空きRAM(または仮想メモリ)です。それはメモリのGbになります。
では、なぜスタックサイズがそれほど制限されているのですか(約1 Mb)?スタック上に本当に大きなオブジェクトを作成することを妨げる技術的な理由は何ですか?
更新:私の意図が明確でない可能性があります。スタックに巨大なオブジェクトを割り当てたくないので、より大きなスタックは必要ありません。この質問は純粋な好奇心です。
回答:
私の直感は次のとおりです。スタックの管理はヒープほど簡単ではありません。スタックは、連続したメモリ位置に格納する必要があります。つまり、必要に応じてスタックをランダムに割り当てることはできませんが、少なくともその目的のために仮想アドレスを予約する必要があります。予約された仮想アドレス空間のサイズが大きいほど、作成できるスレッドは少なくなります。
たとえば、32ビットアプリケーションの仮想アドレス空間は通常2GBです。これは、スタックサイズが2MB(pthreadのデフォルト)の場合、最大1024のスレッドを作成できることを意味します。これは、Webサーバーなどのアプリケーションでは小さい場合があります。スタックサイズをたとえば100MBに増やすと(つまり、100MBを予約しますが、必ずしもすぐに100MBをスタックに割り当てる必要はありません)、スレッド数は約20に制限され、単純なGUIアプリケーションでも制限される可能性があります。
興味深い質問は、64ビットプラットフォームでまだこの制限があるのはなぜかということです。答えはわかりませんが、人々はすでにいくつかの「スタックのベストプラクティス」に慣れていると思います。ヒープに巨大なオブジェクトを割り当てるように注意し、必要に応じて手動でスタックサイズを増やします。したがって、64ビットプラットフォームに「巨大な」スタックサポートを追加することが有用であるとは誰も考えていませんでした。
まだ誰も言及していない1つの側面:
制限されたスタックサイズは、エラー検出および封じ込めメカニズムです。
一般に、CおよびC ++でのスタックの主な仕事は、呼び出しスタックとローカル変数を追跡することであり、スタックが範囲を超えて大きくなると、ほとんどの場合、アプリケーションの設計や動作のエラーになります。 。
スタックが任意に大きくなることが許可されている場合、これらのエラー(無限再帰など)は、オペレーティングシステムのリソースが使い果たされた後でのみ、非常に遅くキャッチされます。これは、スタックサイズに任意の制限を設定することで防止されます。実際のサイズは、システムの劣化を防ぐのに十分小さいことを除けば、それほど重要ではありません。
std::unique_ptr
デストラクタを作成するための(おもちゃの)リストの実装に関するコメントの数に注意してください(スマートポインターに依存しません))。
これは単なるデフォルトサイズです。さらに必要な場合は、より多くを取得できます。ほとんどの場合、リンカーに追加のスタックスペースを割り当てるように指示します。
大きなスタックを持つことの欠点は、多数のスレッドを作成する場合、それぞれに1つのスタックが必要になることです。すべてのスタックがマルチMBを割り当てているが、それを使用していない場合、スペースは無駄になります。
プログラムの適切なバランスを見つける必要があります。
@BJovkeのような一部の人々は、仮想メモリは本質的に無料であると信じています。すべての仮想メモリをバックアップする物理メモリを用意する必要がないのは事実です。少なくとも仮想メモリにアドレスを与えることができなければなりません。
ただし、一般的な32ビットPCでは、仮想メモリのサイズは物理メモリのサイズと同じです。仮想かどうかに関係なく、どのアドレスにも32ビットしかないためです。
プロセス内のすべてのスレッドは同じアドレス空間を共有するため、スレッド間で分割する必要があります。そして、オペレーティングシステムがその役割を果たした後、アプリケーション用に残っているのは「たった」2〜3GBです。そして、そのサイズは、物理メモリと仮想メモリの両方の制限です。これは、アドレスがもうないためです。
一つには、スタックは連続的であるため、12MBを割り当てる場合、作成したものを下回るには、12MBを削除する必要があります。また、オブジェクトを移動するのがはるかに難しくなります。これは、物事を理解しやすくする可能性のある実際の例です。
部屋の周りに箱を積み上げているとしましょう。管理が簡単なもの:
これらの2つの例は大まかな一般化であり、類推で明らかに間違っている点がいくつかありますが、両方の場合の利点を理解するのに役立つことを願っています。
スタックを近いものから遠いものの順に考えてください。レジスタはCPUに近く(高速)、スタックは少し離れており(ただし、まだ比較的近い)、ヒープは遠くにあります(アクセスが遅い)。
スタックはもちろんヒープ上に存在しますが、それでも継続的に使用されているため、おそらくCPUキャッシュを離れることはなく、平均的なヒープアクセスよりも高速になります。これが、スタックを適切なサイズに保つ理由です。可能な限りキャッシュしておくためです。大きなスタックオブジェクトの割り当て(オーバーフローが発生するとスタックのサイズが自動的に変更される可能性があります)は、この原則に反します。
したがって、これは、昔から残っているだけでなく、パフォーマンスの優れたパラダイムです。
大きなスタックが必要だと思うことの多くは、他の方法で行うことができます。
Sedgewickの「Algorithms」には、再帰を反復に置き換えることにより、QuickSortなどの再帰アルゴリズムから再帰を「削除」する良い例がいくつかあります。実際には、アルゴリズムはまだ再帰的であり、スタックとして存在しますが、ランタイムスタックを使用するのではなく、ヒープに並べ替えスタックを割り当てます。
(私はPascalで与えられたアルゴリズムを備えた第2版を好みます。それは8ドルで使用できました。)
別の見方をすれば、大きなスタックが必要だと思う場合、コードは非効率的です。より少ないスタックを使用するより良い方法があります。
技術的な理由はないと思いますが、スタック上に巨大なスーパーオブジェクトを1つだけ作成したのは奇妙なアプリでしょう。スタックオブジェクトには柔軟性がなく、サイズが大きくなると問題が大きくなります。オブジェクトを破棄せずに戻ることはできず、他のスレッドにキューに入れることもできません。
int main() { char buffer[1048576]; }
これは非常に一般的な初心者の問題です。確かに簡単な回避策がありますが、なぜスタックサイズを回避する必要があるのでしょうか。