C#のスタックサイズがちょうど1 MBなのはなぜですか?


102

今日のPCには大量の物理RAMがありますが、それでも、C#のスタックサイズは32ビットプロセスの場合は1 MB、64ビットプロセスの場合は4 MBです(C#のスタック容量)。

CLRのスタックサイズがまだ非常に制限されているのはなぜですか?

そして、なぜそれがちょうど1 MB(4 MB)であるのですか(2 MBや512 KBではないのですか)なぜこれらの金額を使用することにしたのですか?

その決定の背後にある考慮事項と理由に興味があります。


6
64ビットプロセスのデフォルトのスタックサイズは4 MBで、32ビットプロセスの場合は1 MBです。あなたができ、そのPEヘッダーの値を変更することにより、メインスレッドのスタックサイズを変更します。Threadコンストラクタの適切なオーバーロードを使用してスタックサイズを指定することもできます。しかし、これは疑問を投げかけます、なぜより大きなスタックが必要なのですか?
ユヴァルイツチャコフ2015

2
ありがとう、編集。:)問題は、スタックサイズを大きくする方法ではなく、スタックサイズを1 MB(4 MB)に決定する理由です。
ニコライコストフ2015

8
各スレッドはデフォルトでこのスタックサイズを取得するため、ほとんどのスレッドではそれほど必要ありません。PCを起動したところ、システムは現在1200スレッドを実行しています。今すぐ計算してください;)
Lucas Trzesniewski、2015

2
@LucasTrzesniewskiそれだけでなく、メモリ内伝染性でなければなりませ。スタックのサイズが大きいほど、仮想アドレス空間でプロセスが作成できるスレッドが少なくなることに注意してください。
ユヴァルイツチャコフ2015

「正確に」1 MBについては不明:私のWindows 8.1では、.NET Core 3.1コンソールアプリケーションの1572864バイトのデフォルトスタックサイズ(GetCurrentThreadStackLimits Win32 APIを使用して取得)。StackOverflowExceptionなしでstackalloc1500000バイトを処理できます。
George Chakhidze

回答:


210

ここに画像の説明を入力してください

あなたはその選択をした男を見ています。David Cutlerと彼のチームは、デフォルトのスタックサイズとして1メガバイトを選択しました。.NETやC#とは何の関係もありません。これは、Windows NTを作成したときに明らかにされました。1メガバイトは、プログラムのEXEヘッダーまたはCreateThread()winapi呼び出しでスタックサイズが明示的に指定されていない場合に選択されます。これは通常の方法であり、ほとんどすべてのプログラマーがOSに任せてサイズを選択します。

その選択はおそらくWindows NTの設計よりも古いものであり、歴史はこれについてあまりに曖昧です。カトラーがそれについて本を書いてくれればいいのですが、彼は作家ではありませんでした。彼はコンピューターの動作に非常に影響力を持っています。彼の最初のOS設計は、RSC-11Mで、DECコンピュータ(Digital Equipment Corporation)向けの16ビットオペレーティングシステムでした。これは、8ビットマイクロプロセッサ用の最初のまともなOS、Gary KildallのCP / Mに大きな影響を与えました。これはMS-DOSに大きな影響を与えました。

彼の次の設計は、仮想メモリをサポートする32ビットプロセッサ用のオペレーティングシステムであるVMSでした。大成功です。彼の次のものは、会社が崩壊し始めた頃にDECによってキャンセルされ、安価なPCハードウェアと競争することができませんでした。マイクロソフトをキューに入れて、彼らは彼に彼が拒否することができない申し出をしました。彼の同僚の多くも参加しました。彼らはWindows NTとして知られているVMS v2に取り組みました。DECはそれについて動揺しました、それを解決するためにお金が手を変えました。VMSがすでに1メガバイトを選択しているかどうかは、私にはわかりませんが、RSX-11を十分に理解しているだけです。ありそうです。

十分な歴史。1メガバイトは多く、実際のスレッドが数キロバイトを超えることはめったにありません。したがって、メガバイトは実際にはかなり無駄です。ただし、メガバイトは仮想メモリにすぎないので、デマンドページの仮想メモリオペレーティングシステムでは、このような無駄を省くことができます。4096バイトごとに1つずつ、プロセッサに番号を付けます。実際にアドレスを指定するまで、物理メモリ、つまりマシンのRAMを実際に使用することはありません。

1メガバイトのサイズは、もともとネイティブプログラムに対応するために選択されていたため、.NETプログラムではそれは過剰です。大きなスタックフレームを作成する傾向があり、文字列とバッファ(配列)もスタックに格納します。マルウェアの攻撃ベクトルとして悪名高いバッファオーバーフローは、データを使用してプログラムを操作できます。.NETプログラムの動作方法ではなく、文字列と配列がGCヒープに割り当てられ、インデックスがチェックされます。C#でスタックにスペースを割り当てる唯一の方法は、安全でないstackallocキーワードを使用することです。

.NETでのスタックの重要な使用法は、ジッターだけです。スレッドのスタックを使用して、MSILをマシンコードにジャストインタイムでコンパイルします。必要なスペースを確認したことはありません。コードの性質とオプティマイザが有効になっているかどうかによって異なりますが、数十キロバイトはおおよその目安です。それ以外の場合は、このWebサイトの名前が付けられました。.NETプログラムのスタックオーバーフローは非常に致命的です。例外をキャッチしようとするコードを確実にJITするのに十分なスペース(3キロバイト未満)が残っていません。デスクトップへのKaboomが唯一のオプションです。

最後に重要なことですが、.NETプログラムはスタックに対してかなり非生産的なことを行います。CLRはスレッドのスタックをコミットします。これは高価な言葉です。つまり、スタックのサイズを予約するだけでなく、オペレーティングシステムのページングファイルにスペースが確保されるため、必要に応じてスタックを常にスワップアウトできます。コミットに失敗すると致命的なエラーとなり、無条件にプログラムが終了します。これは、RAMが非常に少なく、実行するプロセスが多すぎるマシンでのみ発生します。このようなマシンは、プログラムが終了する前に糖蜜に変換されます。15年以上前の問題であり、今日ではありません。F1レースカーのようにプログラムを調整するプログラマー<disableCommitThreadStack>は、.configファイルの要素を使用します。

Fwiw、Cutlerはオペレーティングシステムの設計をやめませんでした。その写真は、彼がAzureに取り組んでいる間に作成されました。


更新。.NETがスタックをコミットしないことに気付きました。いつ、なぜこれが起こったのか正確にはわかりません。この設計変更は.NET 4.5のどこかで起こったと思います。かなり賢明な変更。


3
あなたのコメントへのrt- The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.ローカル変数は例えばintスタックに格納されていないメソッド内で宣言されていますか?そうだと思います。
RBT

2
OK。スタックフレームは、関数のローカル変数のストレージの唯一の選択肢ではないことがわかりました。それはすることができ、あなたの箇条書きの1で提案されているようにかかわらず、スタックフレームに格納されています。非常に啓発的なハンス。そのような洞察に満ちた投稿を書いてくれてありがとうございます。正直に言うと、スタックは、一般にプログラミングにとって非常に大きな抽象概念であり、不必要な複雑さを回避するためです。
RBT 2017

非常に詳細な説明@ハンス。maxStackSizeスレッドの可能な最小値は何だろうと思っていましたか?[MSDN](msdn.microsoft.com/en-us/library/5cykbwz4 ( v=vs.110 ) .aspx)では見つかりませんでした。あなたのコメントに基づいて、スタックの使用量は絶対的に最小であると思われ、最小の値を使用して可能な最大のスレッドに対応できます。ありがとう。
MKR 2017年

1
@KFL:あなたはそれを試すことによってあなたの質問に簡単に答えることができます!
Eric Lippert、2018年

1
デフォルトの動作はしている場合は、もはやスタックをコミットし、このマークダウンファイルを編集する必要はありませんgithub.com/dotnet/docs/blob/master/docs/framework/...
ジョンStewien

5

デフォルトの予約済みスタックサイズはリンカーによって指定され、開発者はリンク時にPE値を変更するかdwStackSizeCreateThreadWinAPI関数のパラメーターを指定して個々のスレッドに対してそれを上書きできます。

デフォルトのスタックサイズ以上の初期スタックサイズでスレッドを作成すると、最も近い1 MBの倍数に切り上げられます。

値が32ビットプロセスで1 MB、64ビットプロセスで4 MBに等しいのはなぜですか?Windowsを設計した開発者に尋ねるか、誰かがあなたの質問に答えるまで待つべきだと思います。

おそらくマーク・ルシノビッチはそれを知っており、あなたは彼に連絡することができます。この情報は、彼の記事よりもスタックに関する情報が少ない第6版より前のWindows Internalsの本で見つけることができます。あるいは、レイモンドチェンはWindowsの内部とその歴史について興味深いことを書いているため、理由を知っているかもしれません。彼もあなたの質問に答えることができますが、提案ボックスに提案を投稿する必要があります。

しかし、現時点では、MSDN、Mark、Raymondのブログを使用して、Microsoftがこれらの値を選択したと考えられるいくつかの理由を説明しようと思います。

初期にはPCが遅く、スタックへのメモリの割り当てがヒープへのメモリの割り当てよりもはるかに高速であったため、デフォルトにはこれらの値が含まれている可能性があります。スタックの割り当てははるかに安価だったため、使用されましたが、より大きなスタックサイズが必要でした。

したがって、この値は、ほとんどのアプリケーションで最適な予約済みスタックサイズでした。ネストされた多数の呼び出しを行い、スタックにメモリを割り当てて、構造体を呼び出し元の関数に渡すことができるため、最適です。同時に、多くのスレッドを作成することができます。

現在、WinAPI関数にパラメーターとして渡される構造はスタックに割り当てられているため、これらの値は主に下位互換性のために使用されます。ただし、スタック割り当てを使用していない場合、スレッドのスタック使用量はデフォルトの1 MBより大幅に少なくなり、Hans Passantが述べたように無駄になります。これを防ぐために、アプリケーションのPEヘッダーで他が指定されていない場合、OSはスタックの最初のページ(4 KB)のみをコミットします。他のページはオンデマンドで割り当てられます。

一部のアプリケーションは、予約されたアドレス空間を上書きし、最初にメモリ使用量を最適化することを約束します。例として、IISネイティブプロセスのスレッドの最大スタックサイズは256 KB(KB932909)です。そして、このデフォルト値の減少は、マイクロソフトによって推奨されています。

できるだけ小さなスタックサイズを選択し、スレッドまたはファイバーを確実に実行するために必要なスタックをコミットするのが最善です。スタック用に予約されているすべてのページを他の目的に使用することはできません。

出典:

  1. スレッドスタックサイズ(Microsoft Docs)
  2. Windowsの限界に挑む:プロセスとスレッド(Mark Russinovich)
  3. 既定では、ネイティブIISプロセスで作成されるスレッドの最大スタックサイズは256 KB(KB932909)です。

より大きなスタックサイズが必要な場合は、それを設定できます(atalasoft.com/cs/blogs/rickm/archive/2008/04/22/…)。その決定の背後にある考慮事項と理由を知りたいのです。
ニコライコストフ2015

2
はい。今私はあなたを理解します:)デフォルトのスタックサイズは最適であり(@Lucas Trzesniewskiコメントを参照)、割り当ての粒度の最も近い倍数に丸められる必要があります。指定されたスタックサイズがデフォルトのスタックサイズより大きい場合は、最も近い1MBの倍数に切り上げられます。そのため、Microsoftはすべてのユーザーモードアプリケーションのデフォルトのスタックサイズとしてこのサイズを選択しました。そして、他の理由はありません。
Yoh Deadfall

ソースはありますか?ドキュメントはありますか?:)
ニコライコストフ2015

@Yoh興味深いリンク。それを回答に要約する必要があります。
Lucas Trzesniewski
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.